summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorJimmy Huang <jimmy.huang@linux.intel.com>2012-08-31 15:21:38 -0700
committerJimmy Huang <jimmy.huang@linux.intel.com>2012-08-31 15:21:38 -0700
commit30d855cbca8385abefa3ffe95811a2bfaa666cf8 (patch)
treea8831781e15d375080111d26806c2e9b825c4c2f /doc
downloadpython-twisted-30d855cbca8385abefa3ffe95811a2bfaa666cf8.tar.gz
python-twisted-30d855cbca8385abefa3ffe95811a2bfaa666cf8.tar.bz2
python-twisted-30d855cbca8385abefa3ffe95811a2bfaa666cf8.zip
Signed-off-by: Jimmy Huang <jimmy.huang@linux.intel.com>
Diffstat (limited to 'doc')
-rw-r--r--doc/conch/benchmarks/README15
-rwxr-xr-xdoc/conch/benchmarks/buffering_mixin.py182
-rw-r--r--doc/conch/examples/demo.tac25
-rw-r--r--doc/conch/examples/demo_draw.tac80
-rw-r--r--doc/conch/examples/demo_insults.tac252
-rw-r--r--doc/conch/examples/demo_manhole.tac56
-rw-r--r--doc/conch/examples/demo_recvline.tac77
-rw-r--r--doc/conch/examples/demo_scroll.tac100
-rw-r--r--doc/conch/examples/index.html40
-rw-r--r--doc/conch/examples/sshsimpleclient.py111
-rwxr-xr-xdoc/conch/examples/sshsimpleserver.py117
-rw-r--r--doc/conch/examples/telnet_echo.tac47
-rw-r--r--doc/conch/examples/window.tac202
-rw-r--r--doc/conch/howto/conch_client.html321
-rw-r--r--doc/conch/howto/index.html28
-rw-r--r--doc/conch/index.html25
-rw-r--r--doc/conch/man/cftp-man.html87
-rw-r--r--doc/conch/man/cftp.189
-rw-r--r--doc/conch/man/ckeygen-man.html107
-rw-r--r--doc/conch/man/ckeygen.157
-rw-r--r--doc/conch/man/conch-man.html148
-rw-r--r--doc/conch/man/conch.1206
-rw-r--r--doc/conch/man/tkconch-man.html129
-rw-r--r--doc/conch/man/tkconch.172
-rw-r--r--doc/core/benchmarks/banana.py10
-rw-r--r--doc/core/benchmarks/deferreds.py145
-rw-r--r--doc/core/benchmarks/failure.py66
-rw-r--r--doc/core/benchmarks/linereceiver.py47
-rw-r--r--doc/core/benchmarks/netstringreceiver.py242
-rw-r--r--doc/core/benchmarks/task.py26
-rw-r--r--doc/core/benchmarks/timer.py24
-rw-r--r--doc/core/benchmarks/tpclient.py60
-rw-r--r--doc/core/benchmarks/tpclient_nt.py22
-rw-r--r--doc/core/benchmarks/tpserver.py19
-rw-r--r--doc/core/benchmarks/tpserver_nt.py22
-rw-r--r--doc/core/development/index.html26
-rw-r--r--doc/core/development/listings/new_module_template.py12
-rw-r--r--doc/core/development/naming.html38
-rw-r--r--doc/core/development/philosophy.html58
-rw-r--r--doc/core/development/policy/coding-standard.html818
-rw-r--r--doc/core/development/policy/doc-standard.html196
-rw-r--r--doc/core/development/policy/index.html33
-rw-r--r--doc/core/development/policy/svn-dev.html230
-rw-r--r--doc/core/development/policy/test-standard.html399
-rw-r--r--doc/core/development/policy/writing-standard.html313
-rw-r--r--doc/core/development/security.html43
-rw-r--r--doc/core/examples/ampclient.py26
-rw-r--r--doc/core/examples/ampserver.py40
-rw-r--r--doc/core/examples/bananabench.py79
-rw-r--r--doc/core/examples/chatserver.py37
-rw-r--r--doc/core/examples/courier.py111
-rw-r--r--doc/core/examples/cred.py163
-rwxr-xr-xdoc/core/examples/dbcred.py179
-rw-r--r--doc/core/examples/echoclient.py41
-rwxr-xr-xdoc/core/examples/echoclient_ssl.py46
-rw-r--r--doc/core/examples/echoclient_udp.py38
-rw-r--r--doc/core/examples/echoserv.py27
-rw-r--r--doc/core/examples/echoserv_ssl.py30
-rw-r--r--doc/core/examples/echoserv_udp.py19
-rw-r--r--doc/core/examples/filewatch.py17
-rw-r--r--doc/core/examples/ftpclient.py113
-rw-r--r--doc/core/examples/ftpserver.py55
-rw-r--r--doc/core/examples/gpsfix.py78
-rw-r--r--doc/core/examples/index.html125
-rw-r--r--doc/core/examples/longex.py66
-rw-r--r--doc/core/examples/longex2.py101
-rwxr-xr-xdoc/core/examples/mouse.py80
-rw-r--r--doc/core/examples/pb_exceptions.py36
-rw-r--r--doc/core/examples/pbbenchclient.py42
-rw-r--r--doc/core/examples/pbbenchserver.py54
-rw-r--r--doc/core/examples/pbecho.py51
-rw-r--r--doc/core/examples/pbechoclient.py32
-rw-r--r--doc/core/examples/pbgtk2.py122
-rw-r--r--doc/core/examples/pbgtk2login.glade330
-rw-r--r--doc/core/examples/pbinterop.py71
-rw-r--r--doc/core/examples/pbsimple.py16
-rw-r--r--doc/core/examples/pbsimpleclient.py18
-rw-r--r--doc/core/examples/postfix.py29
-rw-r--r--doc/core/examples/ptyserv.py45
-rw-r--r--doc/core/examples/pyui_bg.pngbin0 -> 29913 bytes
-rwxr-xr-xdoc/core/examples/pyuidemo.py39
-rw-r--r--doc/core/examples/recvfd.py90
-rw-r--r--doc/core/examples/rotatinglog.py26
-rw-r--r--doc/core/examples/sendfd.py83
-rw-r--r--doc/core/examples/server.pem36
-rw-r--r--doc/core/examples/shaper.py52
-rw-r--r--doc/core/examples/shoutcast.py26
-rw-r--r--doc/core/examples/simple.tac39
-rw-r--r--doc/core/examples/simpleclient.py49
-rw-r--r--doc/core/examples/simpleserv.py26
-rw-r--r--doc/core/examples/stdin.py30
-rw-r--r--doc/core/examples/stdiodemo.py78
-rw-r--r--doc/core/examples/streaming.py103
-rw-r--r--doc/core/examples/testlogging.py41
-rw-r--r--doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib13
-rw-r--r--doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib24
-rw-r--r--doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nibbin0 -> 14896 bytes
-rw-r--r--doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt6
-rw-r--r--doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py79
-rw-r--r--doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py14
-rw-r--r--doc/core/examples/threadedselect/README15
-rw-r--r--doc/core/examples/threadedselect/blockingdemo.py92
-rw-r--r--doc/core/examples/threadedselect/pygamedemo.py78
-rw-r--r--doc/core/examples/tkinterdemo.py44
-rw-r--r--doc/core/examples/twistd-logging.tac33
-rw-r--r--doc/core/examples/wxacceptance.py113
-rw-r--r--doc/core/examples/wxdemo.py64
-rw-r--r--doc/core/howto/amp.html349
-rw-r--r--doc/core/howto/application.html398
-rw-r--r--doc/core/howto/basics.html100
-rw-r--r--doc/core/howto/book.tex129
-rw-r--r--doc/core/howto/choosing-reactor.html395
-rw-r--r--doc/core/howto/clients.html741
-rw-r--r--doc/core/howto/components.html603
-rw-r--r--doc/core/howto/constants.html456
-rw-r--r--doc/core/howto/cred.html566
-rw-r--r--doc/core/howto/debug-with-emacs.html65
-rw-r--r--doc/core/howto/defer.html898
-rw-r--r--doc/core/howto/design.html254
-rw-r--r--doc/core/howto/dirdbm.html76
-rw-r--r--doc/core/howto/endpoints.html231
-rw-r--r--doc/core/howto/gendefer.html411
-rw-r--r--doc/core/howto/glossary.html334
-rw-r--r--doc/core/howto/howto.tidyrc6
-rw-r--r--doc/core/howto/index.html239
-rw-r--r--doc/core/howto/internet-overview.html48
-rw-r--r--doc/core/howto/listings/TwistedQuotes/__init__.py3
-rw-r--r--doc/core/howto/listings/TwistedQuotes/pbquote.py10
-rw-r--r--doc/core/howto/listings/TwistedQuotes/pbquoteclient.py32
-rw-r--r--doc/core/howto/listings/TwistedQuotes/quoteproto.py36
-rw-r--r--doc/core/howto/listings/TwistedQuotes/quoters.py39
-rw-r--r--doc/core/howto/listings/TwistedQuotes/quotes.txt15
-rw-r--r--doc/core/howto/listings/TwistedQuotes/quotetap.py29
-rw-r--r--doc/core/howto/listings/TwistedQuotes/quotetap2.py36
-rw-r--r--doc/core/howto/listings/TwistedQuotes/webquote.rpy12
-rw-r--r--doc/core/howto/listings/amp/basic_client.py30
-rw-r--r--doc/core/howto/listings/amp/basic_server.tac14
-rw-r--r--doc/core/howto/listings/amp/command_client.py48
-rw-r--r--doc/core/howto/listings/application/service.tac34
-rw-r--r--doc/core/howto/listings/deferred/synch-validation.py5
-rwxr-xr-xdoc/core/howto/listings/pb/cache_classes.py43
-rwxr-xr-xdoc/core/howto/listings/pb/cache_receiver.py28
-rwxr-xr-xdoc/core/howto/listings/pb/cache_sender.py50
-rwxr-xr-xdoc/core/howto/listings/pb/chatclient.py42
-rwxr-xr-xdoc/core/howto/listings/pb/chatserver.py65
-rwxr-xr-xdoc/core/howto/listings/pb/copy2_classes.py29
-rwxr-xr-xdoc/core/howto/listings/pb/copy2_receiver.py21
-rwxr-xr-xdoc/core/howto/listings/pb/copy2_sender.py44
-rwxr-xr-xdoc/core/howto/listings/pb/copy_receiver.tac41
-rwxr-xr-xdoc/core/howto/listings/pb/copy_sender.py57
-rwxr-xr-xdoc/core/howto/listings/pb/exc_client.py33
-rwxr-xr-xdoc/core/howto/listings/pb/exc_server.py32
-rwxr-xr-xdoc/core/howto/listings/pb/pb1client.py31
-rwxr-xr-xdoc/core/howto/listings/pb/pb1server.py20
-rwxr-xr-xdoc/core/howto/listings/pb/pb2client.py36
-rwxr-xr-xdoc/core/howto/listings/pb/pb2server.py30
-rwxr-xr-xdoc/core/howto/listings/pb/pb3client.py26
-rwxr-xr-xdoc/core/howto/listings/pb/pb3server.py16
-rwxr-xr-xdoc/core/howto/listings/pb/pb4client.py58
-rwxr-xr-xdoc/core/howto/listings/pb/pb5client.py22
-rwxr-xr-xdoc/core/howto/listings/pb/pb5server.py29
-rwxr-xr-xdoc/core/howto/listings/pb/pb6client1.py22
-rwxr-xr-xdoc/core/howto/listings/pb/pb6client2.py25
-rwxr-xr-xdoc/core/howto/listings/pb/pb6server.py30
-rwxr-xr-xdoc/core/howto/listings/pb/pb7client.py29
-rwxr-xr-xdoc/core/howto/listings/pb/pbAnonClient.py70
-rwxr-xr-xdoc/core/howto/listings/pb/pbAnonServer.py91
-rwxr-xr-xdoc/core/howto/listings/pb/trap_client.py88
-rwxr-xr-xdoc/core/howto/listings/pb/trap_server.py21
-rwxr-xr-xdoc/core/howto/listings/process/process.py46
-rw-r--r--doc/core/howto/listings/process/quotes.py25
-rw-r--r--doc/core/howto/listings/process/trueandfalse.py14
-rw-r--r--doc/core/howto/listings/sendmsg/copy_descriptor.py35
-rw-r--r--doc/core/howto/listings/sendmsg/send_replacement.py21
-rw-r--r--doc/core/howto/listings/servers/chat.py51
-rw-r--r--doc/core/howto/listings/trial/calculus/__init__.py0
-rw-r--r--doc/core/howto/listings/trial/calculus/base_1.py16
-rw-r--r--doc/core/howto/listings/trial/calculus/base_2.py14
-rw-r--r--doc/core/howto/listings/trial/calculus/base_3.py24
-rw-r--r--doc/core/howto/listings/trial/calculus/client_1.py39
-rw-r--r--doc/core/howto/listings/trial/calculus/client_2.py54
-rw-r--r--doc/core/howto/listings/trial/calculus/client_3.py53
-rw-r--r--doc/core/howto/listings/trial/calculus/remote_1.py47
-rw-r--r--doc/core/howto/listings/trial/calculus/remote_2.py51
-rw-r--r--doc/core/howto/listings/trial/calculus/test/__init__.py0
-rw-r--r--doc/core/howto/listings/trial/calculus/test/test_base_1.py23
-rw-r--r--doc/core/howto/listings/trial/calculus/test/test_base_2.py29
-rw-r--r--doc/core/howto/listings/trial/calculus/test/test_base_2b.py29
-rw-r--r--doc/core/howto/listings/trial/calculus/test/test_base_3.py52
-rw-r--r--doc/core/howto/listings/trial/calculus/test/test_client_1.py37
-rw-r--r--doc/core/howto/listings/trial/calculus/test/test_client_2.py48
-rw-r--r--doc/core/howto/listings/trial/calculus/test/test_client_3.py63
-rw-r--r--doc/core/howto/listings/trial/calculus/test/test_remote_1.py34
-rw-r--r--doc/core/howto/listings/trial/calculus/test/test_remote_2.py46
-rw-r--r--doc/core/howto/listings/trial/calculus/test/test_remote_3.py40
-rw-r--r--doc/core/howto/listings/udp/MulticastClient.py19
-rw-r--r--doc/core/howto/listings/udp/MulticastServer.py28
-rw-r--r--doc/core/howto/logging.html196
-rw-r--r--doc/core/howto/options.html581
-rw-r--r--doc/core/howto/pb-clients.html362
-rw-r--r--doc/core/howto/pb-copyable.html1185
-rw-r--r--doc/core/howto/pb-cred.html1724
-rw-r--r--doc/core/howto/pb-intro.html320
-rw-r--r--doc/core/howto/pb-limits.html51
-rw-r--r--doc/core/howto/pb-usage.html1156
-rw-r--r--doc/core/howto/pb.html52
-rw-r--r--doc/core/howto/plugin.html294
-rw-r--r--doc/core/howto/process.html732
-rw-r--r--doc/core/howto/producers.html90
-rw-r--r--doc/core/howto/quotes.html214
-rw-r--r--doc/core/howto/rdbms.html228
-rw-r--r--doc/core/howto/reactor-basics.html93
-rw-r--r--doc/core/howto/sendmsg.html221
-rw-r--r--doc/core/howto/servers.html548
-rw-r--r--doc/core/howto/ssl.html550
-rw-r--r--doc/core/howto/stylesheet-unprocessed.css20
-rw-r--r--doc/core/howto/stylesheet.css189
-rw-r--r--doc/core/howto/tap.html323
-rw-r--r--doc/core/howto/template.tpl23
-rw-r--r--doc/core/howto/testing.html167
-rw-r--r--doc/core/howto/threading.html213
-rw-r--r--doc/core/howto/time.html118
-rw-r--r--doc/core/howto/trial.html2042
-rw-r--r--doc/core/howto/tutorial/backends.html1348
-rw-r--r--doc/core/howto/tutorial/client.html260
-rw-r--r--doc/core/howto/tutorial/components.html1132
-rw-r--r--doc/core/howto/tutorial/configuration.html870
-rw-r--r--doc/core/howto/tutorial/factory.html713
-rw-r--r--doc/core/howto/tutorial/index.html83
-rw-r--r--doc/core/howto/tutorial/intro.html725
-rw-r--r--doc/core/howto/tutorial/library.html271
-rw-r--r--doc/core/howto/tutorial/listings/finger/etc.users2
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger/__init__.py3
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger/finger.py368
-rw-r--r--doc/core/howto/tutorial/listings/finger/finger/tap.py20
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger01.py2
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger02.py10
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger03.py11
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger04.py12
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger05.py13
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger06.py18
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger07.py21
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger08.py30
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger09.py26
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger10.py30
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger11.tac34
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger12.tac55
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger13.tac59
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger14.tac56
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger15.tac87
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger16.tac101
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger17.tac102
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger18.tac147
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger19.tac270
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger19a.tac231
-rw-r--r--doc/core/howto/tutorial/listings/finger/finger19a_changes.py29
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger19b.tac292
-rw-r--r--doc/core/howto/tutorial/listings/finger/finger19b_changes.py19
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger19c.tac305
-rw-r--r--doc/core/howto/tutorial/listings/finger/finger19c_changes.py32
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger20.tac285
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger21.tac319
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/finger22.py337
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/fingerPBclient.py26
-rwxr-xr-xdoc/core/howto/tutorial/listings/finger/fingerXRclient.py5
-rw-r--r--doc/core/howto/tutorial/listings/finger/finger_config.py38
-rw-r--r--doc/core/howto/tutorial/listings/finger/fingerproxy.tac110
-rw-r--r--doc/core/howto/tutorial/listings/finger/organized-finger.tac31
-rw-r--r--doc/core/howto/tutorial/listings/finger/simple-finger.tac17
-rw-r--r--doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py5
-rw-r--r--doc/core/howto/tutorial/pb.html728
-rw-r--r--doc/core/howto/tutorial/protocol.html1121
-rw-r--r--doc/core/howto/tutorial/style.html333
-rw-r--r--doc/core/howto/tutorial/web.html610
-rw-r--r--doc/core/howto/udp.html304
-rw-r--r--doc/core/howto/vision.html43
-rw-r--r--doc/core/img/TwistedLogo.bmpbin0 -> 55494 bytes
-rw-r--r--doc/core/img/cred-login.diabin0 -> 2369 bytes
-rw-r--r--doc/core/img/cred-login.pngbin0 -> 34148 bytes
-rw-r--r--doc/core/img/deferred-attach.diabin0 -> 2234 bytes
-rw-r--r--doc/core/img/deferred-attach.pngbin0 -> 9356 bytes
-rw-r--r--doc/core/img/deferred-process.diabin0 -> 2099 bytes
-rw-r--r--doc/core/img/deferred-process.pngbin0 -> 10809 bytes
-rw-r--r--doc/core/img/deferred-states.svg3
-rw-r--r--doc/core/img/deferred.diabin0 -> 4348 bytes
-rw-r--r--doc/core/img/deferred.pngbin0 -> 33282 bytes
-rw-r--r--doc/core/index.html31
-rw-r--r--doc/core/man/manhole-man.html50
-rw-r--r--doc/core/man/manhole.116
-rw-r--r--doc/core/man/pyhtmlizer-man.html51
-rw-r--r--doc/core/man/pyhtmlizer.122
-rw-r--r--doc/core/man/tap2deb-man.html101
-rw-r--r--doc/core/man/tap2deb.155
-rw-r--r--doc/core/man/tap2rpm-man.html100
-rw-r--r--doc/core/man/tap2rpm.155
-rw-r--r--doc/core/man/tapconvert-man.html82
-rw-r--r--doc/core/man/tapconvert.140
-rw-r--r--doc/core/man/trial-man.html275
-rw-r--r--doc/core/man/trial.1200
-rw-r--r--doc/core/man/twistd-man.html187
-rw-r--r--doc/core/man/twistd.1118
-rw-r--r--doc/core/specifications/banana.html199
-rw-r--r--doc/core/specifications/index.html21
-rw-r--r--doc/fun/Twisted.Quotes0
-rw-r--r--doc/fun/lightbulb7
-rw-r--r--doc/fun/register.html77
-rw-r--r--doc/historic/2002/ipc10/twisted-network-framework/errata.html256
-rw-r--r--doc/historic/2002/ipc10/twisted-network-framework/index.html1568
-rw-r--r--doc/historic/2003/europython/doanddont.html508
-rw-r--r--doc/historic/2003/europython/index.html35
-rw-r--r--doc/historic/2003/europython/lore.html502
-rw-r--r--doc/historic/2003/europython/slides-template.tpl19
-rw-r--r--doc/historic/2003/europython/tw-deploy.html1106
-rw-r--r--doc/historic/2003/europython/twisted.html608
-rw-r--r--doc/historic/2003/europython/webclients.html482
-rw-r--r--doc/historic/2003/haifux/haifux.html2235
-rw-r--r--doc/historic/2003/haifux/notes.html60
-rwxr-xr-xdoc/historic/2003/pycon/applications/applications257
-rw-r--r--doc/historic/2003/pycon/applications/applications.html343
-rw-r--r--doc/historic/2003/pycon/applications/pynfo-chart.pngbin0 -> 13018 bytes
-rwxr-xr-xdoc/historic/2003/pycon/conch/conch98
-rw-r--r--doc/historic/2003/pycon/conch/conch.html165
-rwxr-xr-xdoc/historic/2003/pycon/conch/conchtalk.txt144
-rw-r--r--doc/historic/2003/pycon/conch/smalltwisted.pngbin0 -> 1472 bytes
-rw-r--r--doc/historic/2003/pycon/conch/twistedlogo.pngbin0 -> 7256 bytes
-rw-r--r--doc/historic/2003/pycon/deferex/deferex-bad-adding.py8
-rw-r--r--doc/historic/2003/pycon/deferex/deferex-chaining.py13
-rw-r--r--doc/historic/2003/pycon/deferex/deferex-complex-failure.py30
-rw-r--r--doc/historic/2003/pycon/deferex/deferex-complex-raise.py12
-rw-r--r--doc/historic/2003/pycon/deferex/deferex-forwarding.py9
-rw-r--r--doc/historic/2003/pycon/deferex/deferex-listing0.py18
-rw-r--r--doc/historic/2003/pycon/deferex/deferex-listing1.py6
-rw-r--r--doc/historic/2003/pycon/deferex/deferex-listing2.py8
-rw-r--r--doc/historic/2003/pycon/deferex/deferex-simple-failure.py9
-rw-r--r--doc/historic/2003/pycon/deferex/deferex-simple-raise.py3
-rw-r--r--doc/historic/2003/pycon/deferex/deferex.html499
-rw-r--r--doc/historic/2003/pycon/deferex/deferexex.py16
-rw-r--r--doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning97
-rwxr-xr-xdoc/historic/2003/pycon/lore/lore-presentation108
-rwxr-xr-xdoc/historic/2003/pycon/lore/lore-slides.html187
-rw-r--r--doc/historic/2003/pycon/lore/lore.html791
-rwxr-xr-xdoc/historic/2003/pycon/pb/pb-client1.py46
-rwxr-xr-xdoc/historic/2003/pycon/pb/pb-server1.py16
-rwxr-xr-xdoc/historic/2003/pycon/pb/pb-slides.py240
-rw-r--r--doc/historic/2003/pycon/pb/pb.html966
-rwxr-xr-xdoc/historic/2003/pycon/releasing/releasing-twisted151
-rw-r--r--doc/historic/2003/pycon/releasing/releasing.html491
-rwxr-xr-xdoc/historic/2003/pycon/tw-deploy/tw-deploy184
-rw-r--r--doc/historic/2003/pycon/tw-deploy/twisted-overview.pngbin0 -> 12722 bytes
-rw-r--r--doc/historic/2003/pycon/tw-deploy/twistedlogo.pngbin0 -> 7256 bytes
-rw-r--r--doc/historic/2003/pycon/twisted-internet/twisted-internet.py541
-rw-r--r--doc/historic/2003/pycon/twisted-reality/componentized.svg254
-rw-r--r--doc/historic/2003/pycon/twisted-reality/twisted-reality.html578
-rw-r--r--doc/historic/2004/ibm/talk.html495
-rw-r--r--doc/historic/FirstTenYears.Quotes5816
-rw-r--r--doc/historic/Twisted-12.1.0.Quotes24
-rw-r--r--doc/historic/index.html128
-rw-r--r--doc/historic/ipc10errata.html256
-rw-r--r--doc/historic/ipc10paper.html1568
-rw-r--r--doc/historic/stylesheet.css178
-rw-r--r--doc/historic/template-notoc.tpl14
-rw-r--r--doc/historic/template.tpl20
-rw-r--r--doc/historic/twisted-debian.html96
-rw-r--r--doc/index.xhtml115
-rw-r--r--doc/lore/examples/example.html60
-rw-r--r--doc/lore/examples/index.html22
-rw-r--r--doc/lore/examples/slides-template.tpl21
-rw-r--r--doc/lore/howto/extend-lore.html427
-rw-r--r--doc/lore/howto/index.html23
-rw-r--r--doc/lore/howto/listings/lore/1st_example.html12
-rw-r--r--doc/lore/howto/listings/lore/a_lore_plugin.py11
-rw-r--r--doc/lore/howto/listings/lore/factory.py-19
-rw-r--r--doc/lore/howto/listings/lore/factory.py-220
-rw-r--r--doc/lore/howto/listings/lore/factory.py-321
-rw-r--r--doc/lore/howto/listings/lore/spitters.py-118
-rw-r--r--doc/lore/howto/listings/lore/spitters.py-226
-rw-r--r--doc/lore/howto/lore.html369
-rw-r--r--doc/lore/img/myhtml-output.pngbin0 -> 23124 bytes
-rw-r--r--doc/lore/index.html25
-rw-r--r--doc/lore/man/lore-man.html124
-rw-r--r--doc/lore/man/lore.174
-rw-r--r--doc/mail/examples/emailserver.tac107
-rw-r--r--doc/mail/examples/imap4client.py181
-rw-r--r--doc/mail/examples/index.html36
-rw-r--r--doc/mail/examples/smtpclient_simple.py47
-rw-r--r--doc/mail/examples/smtpclient_tls.py157
-rw-r--r--doc/mail/index.html25
-rw-r--r--doc/mail/man/mailmail-man.html55
-rw-r--r--doc/mail/man/mailmail.121
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-1.tac3
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-10.tac56
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-11.tac58
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-2.tac10
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-3.tac10
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-4.tac12
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-5.tac14
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-6.tac18
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-7.tac46
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-8.tac49
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient-9.tac53
-rw-r--r--doc/mail/tutorial/smtpclient/smtpclient.html757
-rw-r--r--doc/mail/tutorial/smtpserver/smtpserver-1.tac3
-rw-r--r--doc/mail/tutorial/smtpserver/smtpserver-2.tac10
-rw-r--r--doc/mail/tutorial/smtpserver/smtpserver-3.tac12
-rw-r--r--doc/mail/tutorial/smtpserver/smtpserver-4.tac14
-rw-r--r--doc/mail/tutorial/smtpserver/smtpserver-5.tac50
-rw-r--r--doc/mail/tutorial/smtpserver/smtpserver-6.tac57
-rw-r--r--doc/mail/tutorial/smtpserver/smtpserver-7.tac57
-rw-r--r--doc/mail/tutorial/smtpserver/smtpserver-8.tac63
-rwxr-xr-xdoc/names/examples/dns-service.py45
-rwxr-xr-xdoc/names/examples/gethostbyname.py28
-rw-r--r--doc/names/examples/index.html24
-rw-r--r--doc/names/examples/testdns.py48
-rw-r--r--doc/names/howto/index.html22
-rw-r--r--doc/names/howto/listings/names/example-domain.com37
-rw-r--r--doc/names/howto/names.html134
-rw-r--r--doc/names/index.html25
-rw-r--r--doc/pair/examples/index.html23
-rw-r--r--doc/pair/examples/pairudp.py21
-rw-r--r--doc/pair/howto/index.html27
-rw-r--r--doc/pair/howto/twisted-pair.html79
-rw-r--r--doc/pair/index.html25
-rw-r--r--doc/stylesheet.css189
-rw-r--r--doc/web/examples/advogato.py46
-rw-r--r--doc/web/examples/dlpage.py23
-rw-r--r--doc/web/examples/fortune.rpy.py49
-rw-r--r--doc/web/examples/getpage.py20
-rw-r--r--doc/web/examples/google.py21
-rw-r--r--doc/web/examples/hello.rpy.py38
-rw-r--r--doc/web/examples/httpclient.py72
-rw-r--r--doc/web/examples/index.html100
-rw-r--r--doc/web/examples/lj.rpy.py48
-rw-r--r--doc/web/examples/logging-proxy.py45
-rw-r--r--doc/web/examples/proxy.py26
-rw-r--r--doc/web/examples/report.rpy.py44
-rw-r--r--doc/web/examples/reverse-proxy.py18
-rw-r--r--doc/web/examples/rootscript.py41
-rw-r--r--doc/web/examples/silly-web.py28
-rw-r--r--doc/web/examples/simple.rtl32
-rw-r--r--doc/web/examples/soap.py44
-rw-r--r--doc/web/examples/users.rpy.py24
-rw-r--r--doc/web/examples/web.py29
-rw-r--r--doc/web/examples/webguard.py64
-rw-r--r--doc/web/examples/xmlrpc.py80
-rw-r--r--doc/web/examples/xmlrpcclient.py31
-rw-r--r--doc/web/howto/client.html1088
-rw-r--r--doc/web/howto/glossary.html42
-rw-r--r--doc/web/howto/index.html52
-rw-r--r--doc/web/howto/listings/client/cookies.py25
-rw-r--r--doc/web/howto/listings/client/filesendbody.py26
-rw-r--r--doc/web/howto/listings/client/gzipdecoder.py43
-rw-r--r--doc/web/howto/listings/client/request.py21
-rw-r--r--doc/web/howto/listings/client/response.py47
-rw-r--r--doc/web/howto/listings/client/sendbody.py24
-rw-r--r--doc/web/howto/listings/client/stringprod.py21
-rw-r--r--doc/web/howto/listings/element_1.py13
-rw-r--r--doc/web/howto/listings/element_2.py13
-rw-r--r--doc/web/howto/listings/element_3.py13
-rw-r--r--doc/web/howto/listings/iteration-1.py17
-rw-r--r--doc/web/howto/listings/iteration-1.xml3
-rw-r--r--doc/web/howto/listings/iteration-output-1.xml3
-rw-r--r--doc/web/howto/listings/output-1.html9
-rw-r--r--doc/web/howto/listings/output-2.html9
-rw-r--r--doc/web/howto/listings/output-3.html9
-rw-r--r--doc/web/howto/listings/quoting-output.html9
-rw-r--r--doc/web/howto/listings/quoting_element.py13
-rw-r--r--doc/web/howto/listings/render_1.py5
-rw-r--r--doc/web/howto/listings/render_2.py5
-rw-r--r--doc/web/howto/listings/render_3.py5
-rw-r--r--doc/web/howto/listings/render_quoting.py5
-rw-r--r--doc/web/howto/listings/render_slots_attrs.py5
-rw-r--r--doc/web/howto/listings/render_transparent.py5
-rw-r--r--doc/web/howto/listings/slots-attributes-1.xml6
-rw-r--r--doc/web/howto/listings/slots-attributes-output.html4
-rw-r--r--doc/web/howto/listings/slots_attributes_1.py12
-rw-r--r--doc/web/howto/listings/soap.rpy13
-rw-r--r--doc/web/howto/listings/subviews-1.py27
-rw-r--r--doc/web/howto/listings/subviews-1.xml3
-rw-r--r--doc/web/howto/listings/subviews-output-1.xml3
-rw-r--r--doc/web/howto/listings/template-1.xml9
-rw-r--r--doc/web/howto/listings/transparent-1.xml6
-rw-r--r--doc/web/howto/listings/transparent-output.html5
-rw-r--r--doc/web/howto/listings/transparent_element.py13
-rw-r--r--doc/web/howto/listings/wait_for_it.py32
-rw-r--r--doc/web/howto/listings/waited-for-it.html8
-rw-r--r--doc/web/howto/listings/waited-for-it.txt8
-rw-r--r--doc/web/howto/listings/webquote.rtl20
-rw-r--r--doc/web/howto/listings/xmlAndSoapQuote.py25
-rw-r--r--doc/web/howto/listings/xmlquote.rpy12
-rw-r--r--doc/web/howto/listings/xmlrpc-customized.py60
-rw-r--r--doc/web/howto/resource-templates.html108
-rw-r--r--doc/web/howto/twisted-templates.html704
-rw-r--r--doc/web/howto/using-twistedweb.html1074
-rw-r--r--doc/web/howto/web-development.html107
-rw-r--r--doc/web/howto/web-in-60/asynchronous-deferred.html173
-rw-r--r--doc/web/howto/web-in-60/asynchronous.html128
-rw-r--r--doc/web/howto/web-in-60/custom-codes.html116
-rw-r--r--doc/web/howto/web-in-60/dynamic-content.html124
-rw-r--r--doc/web/howto/web-in-60/dynamic-dispatch.html143
-rw-r--r--doc/web/howto/web-in-60/error-handling.html130
-rw-r--r--doc/web/howto/web-in-60/handling-posts.html142
-rw-r--r--doc/web/howto/web-in-60/http-auth.html256
-rw-r--r--doc/web/howto/web-in-60/index.html44
-rw-r--r--doc/web/howto/web-in-60/interrupted.html147
-rw-r--r--doc/web/howto/web-in-60/logging-errors.html107
-rw-r--r--doc/web/howto/web-in-60/rpy-scripts.html90
-rw-r--r--doc/web/howto/web-in-60/session-basics.html121
-rw-r--r--doc/web/howto/web-in-60/session-endings.html170
-rw-r--r--doc/web/howto/web-in-60/session-store.html181
-rw-r--r--doc/web/howto/web-in-60/static-content.html102
-rw-r--r--doc/web/howto/web-in-60/static-dispatch.html119
-rw-r--r--doc/web/howto/web-in-60/wsgi.html125
-rw-r--r--doc/web/howto/web-overview.html67
-rw-r--r--doc/web/howto/xmlrpc.html651
-rw-r--r--doc/web/img/controller.pngbin0 -> 14934 bytes
-rw-r--r--doc/web/img/livepage.pngbin0 -> 9363 bytes
-rw-r--r--doc/web/img/model.pngbin0 -> 14971 bytes
-rw-r--r--doc/web/img/plone_root_model.pngbin0 -> 11214 bytes
-rw-r--r--doc/web/img/view.pngbin0 -> 14703 bytes
-rw-r--r--doc/web/img/web-overview.diabin0 -> 1630 bytes
-rw-r--r--doc/web/img/web-overview.pngbin0 -> 7330 bytes
-rw-r--r--doc/web/img/web-process.pngbin0 -> 30404 bytes
-rw-r--r--doc/web/img/web-process.svg594
-rw-r--r--doc/web/img/web-session.pngbin0 -> 8966 bytes
-rw-r--r--doc/web/img/web-widgets.diabin0 -> 1326 bytes
-rw-r--r--doc/web/img/web-widgets.pngbin0 -> 3147 bytes
-rw-r--r--doc/web/index.html25
-rw-r--r--doc/words/examples/cursesclient.py188
-rw-r--r--doc/words/examples/index.html29
-rw-r--r--doc/words/examples/ircLogBot.py158
-rw-r--r--doc/words/examples/jabber_client.py29
-rw-r--r--doc/words/examples/minchat.py140
-rw-r--r--doc/words/examples/msn_example.py67
-rwxr-xr-xdoc/words/examples/oscardemo.py100
-rw-r--r--doc/words/examples/pb_client.py102
-rw-r--r--doc/words/examples/xmpp_client.py82
-rw-r--r--doc/words/howto/im.html98
-rw-r--r--doc/words/howto/index.html22
-rw-r--r--doc/words/index.html25
539 files changed, 79076 insertions, 0 deletions
diff --git a/doc/conch/benchmarks/README b/doc/conch/benchmarks/README
new file mode 100644
index 0000000..233bc8e
--- /dev/null
+++ b/doc/conch/benchmarks/README
@@ -0,0 +1,15 @@
+This directory contains various simple programs intended to exercise various
+features of Twisted Conch as a way to learn about and track their
+performance characteristics. As there is currently no record of past
+benchmark results, the tracking aspect of this is currently somewhat
+fantastic. However, the intent is for this to change at some future point.
+
+All (one) of the programs in this directory are currently intended to be
+invoked directly and to report some timing information on standard out.
+
+The following benchmarks are currently available:
+
+buffering_mixin.py:
+
+ This deals with twisted.conch.mixin.BufferingMixin which provides
+ Nagle-like write coalescing for Protocol classes.
diff --git a/doc/conch/benchmarks/buffering_mixin.py b/doc/conch/benchmarks/buffering_mixin.py
new file mode 100755
index 0000000..7009df8
--- /dev/null
+++ b/doc/conch/benchmarks/buffering_mixin.py
@@ -0,0 +1,182 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Benchmarks comparing the write performance of a "normal" Protocol instance
+and an instance of a Protocol class which has had L{twisted.conch.mixin}'s
+L{BufferingMixin<twisted.conch.mixin.BufferingMixin>} mixed in to perform
+Nagle-like write coalescing.
+"""
+
+from sys import stdout
+from pprint import pprint
+from time import time
+
+from twisted.python.usage import Options
+from twisted.python.log import startLogging
+
+from twisted.internet.protocol import ServerFactory, Protocol, ClientCreator
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
+
+from twisted.conch.mixin import BufferingMixin
+
+
+class BufferingBenchmark(Options):
+ """
+ Options for configuring the execution parameters of a benchmark run.
+ """
+
+ optParameters = [
+ ('scale', 's', '1',
+ 'Work multiplier (bigger takes longer, might resist noise better)')]
+
+ def postOptions(self):
+ self['scale'] = int(self['scale'])
+
+
+
+class ServerProtocol(Protocol):
+ """
+ A silent protocol which only waits for a particular amount of input and
+ then fires a Deferred.
+ """
+ def __init__(self, expected, finished):
+ self.expected = expected
+ self.finished = finished
+
+
+ def dataReceived(self, bytes):
+ self.expected -= len(bytes)
+ if self.expected == 0:
+ finished, self.finished = self.finished, None
+ finished.callback(None)
+
+
+
+class BufferingProtocol(Protocol, BufferingMixin):
+ """
+ A protocol which uses the buffering mixin to provide a write method.
+ """
+
+
+
+class UnbufferingProtocol(Protocol):
+ """
+ A protocol which provides a naive write method which simply passes through
+ to the transport.
+ """
+
+ def connectionMade(self):
+ """
+ Bind write to the transport's write method and flush to a no-op
+ function in order to provide the same API as is provided by
+ BufferingProtocol.
+ """
+ self.write = self.transport.write
+ self.flush = lambda: None
+
+
+
+def _write(proto, byteCount):
+ write = proto.write
+ flush = proto.flush
+
+ for i in range(byteCount):
+ write('x')
+ flush()
+
+
+
+def _benchmark(byteCount, clientProtocol):
+ result = {}
+ finished = Deferred()
+ def cbFinished(ignored):
+ result[u'disconnected'] = time()
+ result[u'duration'] = result[u'disconnected'] - result[u'connected']
+ return result
+ finished.addCallback(cbFinished)
+
+ f = ServerFactory()
+ f.protocol = lambda: ServerProtocol(byteCount, finished)
+ server = reactor.listenTCP(0, f)
+
+ f2 = ClientCreator(reactor, clientProtocol)
+ proto = f2.connectTCP('127.0.0.1', server.getHost().port)
+ def connected(proto):
+ result[u'connected'] = time()
+ return proto
+ proto.addCallback(connected)
+ proto.addCallback(_write, byteCount)
+ return finished
+
+
+
+def _benchmarkBuffered(byteCount):
+ return _benchmark(byteCount, BufferingProtocol)
+
+
+
+def _benchmarkUnbuffered(byteCount):
+ return _benchmark(byteCount, UnbufferingProtocol)
+
+
+
+def benchmark(scale=1):
+ """
+ Benchmark and return information regarding the relative performance of a
+ protocol which does not use the buffering mixin and a protocol which
+ does.
+
+ @type scale: C{int}
+ @param scale: A multipler to the amount of work to perform
+
+ @return: A Deferred which will fire with a dictionary mapping each of
+ the two unicode strings C{u'buffered'} and C{u'unbuffered'} to
+ dictionaries describing the performance of a protocol of each type.
+ These value dictionaries will map the unicode strings C{u'connected'}
+ and C{u'disconnected'} to the times at which each of those events
+ occurred and C{u'duration'} two the difference between these two values.
+ """
+ overallResult = {}
+
+ byteCount = 1024
+
+ bufferedDeferred = _benchmarkBuffered(byteCount * scale)
+ def didBuffered(bufferedResult):
+ overallResult[u'buffered'] = bufferedResult
+ unbufferedDeferred = _benchmarkUnbuffered(byteCount * scale)
+ def didUnbuffered(unbufferedResult):
+ overallResult[u'unbuffered'] = unbufferedResult
+ return overallResult
+ unbufferedDeferred.addCallback(didUnbuffered)
+ return unbufferedDeferred
+ bufferedDeferred.addCallback(didBuffered)
+ return bufferedDeferred
+
+
+
+def main(args=None):
+ """
+ Perform a single benchmark run, starting and stopping the reactor and
+ logging system as necessary.
+ """
+ startLogging(stdout)
+
+ options = BufferingBenchmark()
+ options.parseOptions(args)
+
+ d = benchmark(options['scale'])
+ def cbBenchmark(result):
+ pprint(result)
+ def ebBenchmark(err):
+ print err.getTraceback()
+ d.addCallbacks(cbBenchmark, ebBenchmark)
+ def stopReactor(ign):
+ reactor.stop()
+ d.addBoth(stopReactor)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/conch/examples/demo.tac b/doc/conch/examples/demo.tac
new file mode 100644
index 0000000..8eb492c
--- /dev/null
+++ b/doc/conch/examples/demo.tac
@@ -0,0 +1,25 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo.tac
+
+"""Nearly pointless demonstration of the manhole interactive interpreter.
+
+This does about the same thing as demo_manhole, but uses the tap
+module's makeService method instead. The only interesting difference
+is that in this version, the telnet server also requires
+authentication.
+
+Note, you will have to create a file named \"passwd\" and populate it
+with credentials (in the format of passwd(5)) to use this demo.
+"""
+
+from twisted.application import service
+application = service.Application("TAC Demo")
+
+from twisted.conch import manhole_tap
+manhole_tap.makeService({"telnetPort": "tcp:6023",
+ "sshPort": "tcp:6022",
+ "namespace": {"foo": "bar"},
+ "passwd": "passwd"}).setServiceParent(application)
diff --git a/doc/conch/examples/demo_draw.tac b/doc/conch/examples/demo_draw.tac
new file mode 100644
index 0000000..d80b064
--- /dev/null
+++ b/doc/conch/examples/demo_draw.tac
@@ -0,0 +1,80 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_draw.tac
+
+"""A trivial drawing application.
+
+Clients are allowed to connect and spew various characters out over
+the terminal. Spacebar changes the drawing character, while the arrow
+keys move the cursor.
+"""
+
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+class Draw(insults.TerminalProtocol):
+ """Protocol which accepts arrow key and spacebar input and places
+ the requested characters onto the terminal.
+ """
+ cursors = list('!@#$%^&*()_+-=')
+
+ def connectionMade(self):
+ self.terminal.eraseDisplay()
+ self.terminal.resetModes([insults.modes.IRM])
+ self.cursor = self.cursors[0]
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == self.terminal.UP_ARROW:
+ self.terminal.cursorUp()
+ elif keyID == self.terminal.DOWN_ARROW:
+ self.terminal.cursorDown()
+ elif keyID == self.terminal.LEFT_ARROW:
+ self.terminal.cursorBackward()
+ elif keyID == self.terminal.RIGHT_ARROW:
+ self.terminal.cursorForward()
+ elif keyID == ' ':
+ self.cursor = self.cursors[(self.cursors.index(self.cursor) + 1) % len(self.cursors)]
+ else:
+ return
+ self.terminal.write(self.cursor)
+ self.terminal.cursorBackward()
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Insults Demo App")
+makeService({'protocolFactory': Draw,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/examples/demo_insults.tac b/doc/conch/examples/demo_insults.tac
new file mode 100644
index 0000000..ebb01c5
--- /dev/null
+++ b/doc/conch/examples/demo_insults.tac
@@ -0,0 +1,252 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_insults.tac
+
+"""Various simple terminal manipulations using the insults module.
+
+This demo sets up two listening ports: one on 6022 which accepts ssh
+connections; one on 6023 which accepts telnet connections. No login
+for the telnet server is required; for the ssh server, \"username\" is
+the username and \"password\" is the password.
+
+The TerminalProtocol subclass defined here ignores most user input
+(except to print it out to the server log) and spends the duration of
+the connection drawing (the author's humble approximation of)
+raindrops at random locations on the client's terminal. +, -, *, and
+/ are respected and each adjusts an aspect of the timing of the
+animation process.
+"""
+
+import random, string
+
+from twisted.python import log
+from twisted.internet import protocol, task
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+class DrawingFinished(Exception):
+ """Sentinel exception, raised when no \"frames\" for a particular
+ \"animation\" remain to be drawn.
+ """
+
+class Drawable:
+ """Representation of an animation.
+
+ Constructed with a protocol instance and a coordinate on the
+ screen, waits for invocations of iterate() at which point it
+ erases the previous frame of the animation and draws the next one,
+ using its protocol instance and always placing the upper left hand
+ corner of the frame at the given coordinates.
+
+ Frames are defined with draw_ prefixed methods. Erasure is
+ performed by erase_ prefixed methods.
+ """
+ n = 0
+
+ def __init__(self, proto, col, line):
+ self.proto = proto
+ self.col = col
+ self.line = line
+
+ def drawLines(self, s):
+ lines = s.splitlines()
+ c = self.col
+ line = self.line
+ for l in lines:
+ self.proto.cursorPosition(c - len(lines) / 2, line)
+ self.proto.write(l)
+ line += 1
+
+ def iterate(self):
+ getattr(self, 'erase_' + str(self.n))()
+ self.n += 1
+ f = getattr(self, 'draw_' + str(self.n), None)
+ if f is None:
+ raise DrawingFinished()
+ f()
+
+ def erase_0(self):
+ pass
+
+
+class Splat(Drawable):
+ HEIGHT = 5
+ WIDTH = 11
+
+ def draw_1(self):
+ # . .
+ #. . .
+ # . .
+ self.drawLines(' . .\n. . .\n . .')
+
+ def erase_1(self):
+ self.drawLines(' \n \n ')
+
+ def draw_2(self):
+ # . . . .
+ # . o o o .
+ #. o o o o .
+ # . o o o .
+ # . . . .
+ self.drawLines(' . . . .\n . o o o .\n. o o o o .\n . o o o .\n . . . .')
+
+ def erase_2(self):
+ self.drawLines(' \n \n \n \n ')
+
+ def draw_3(self):
+ # o o o o
+ # o O O O o
+ #o O O O O o
+ # o O O O o
+ # o o o o
+ self.drawLines(' o o o o\n o O O O o\no O O O O o\n o O O O o\n o o o o')
+
+ erase_3 = erase_2
+
+ def draw_4(self):
+ # O O O O
+ # O . . . O
+ #O . . . . O
+ # O . . . O
+ # O O O O
+ self.drawLines(' O O O O\n O . . . O\nO . . . . O\n O . . . O\n O O O O')
+
+ erase_4 = erase_3
+
+ def draw_5(self):
+ # . . . .
+ # . .
+ #. .
+ # . .
+ # . . . .
+ self.drawLines(' . . . .\n . .\n. .\n . .\n . . . .')
+
+ erase_5 = erase_4
+
+class Drop(Drawable):
+ WIDTH = 3
+ HEIGHT = 4
+
+ def draw_1(self):
+ # o
+ self.drawLines(' o')
+
+ def erase_1(self):
+ self.drawLines(' ')
+
+ def draw_2(self):
+ # _
+ #/ \
+ #\./
+ self.drawLines(' _ \n/ \\\n\\./')
+
+ def erase_2(self):
+ self.drawLines(' \n \n ')
+
+ def draw_3(self):
+ # O
+ self.drawLines(' O')
+
+ def erase_3(self):
+ self.drawLines(' ')
+
+class DemoProtocol(insults.TerminalProtocol):
+ """Draws random things at random places on the screen.
+ """
+ width = 80
+ height = 24
+
+ interval = 0.1
+ rate = 0.05
+
+ def connectionMade(self):
+ self.run()
+
+ def connectionLost(self, reason):
+ self._call.stop()
+ del self._call
+
+ def run(self):
+ # Clear the screen, matey
+ self.terminal.eraseDisplay()
+
+ self._call = task.LoopingCall(self._iterate)
+ self._call.start(self.interval)
+
+ def _iterate(self):
+ cls = random.choice((Splat, Drop))
+
+ # Move to a random location on the screen
+ col = random.randrange(self.width - cls.WIDTH) + cls.WIDTH
+ line = random.randrange(self.height - cls.HEIGHT) + cls.HEIGHT
+
+ s = cls(self.terminal, col, line)
+
+ c = task.LoopingCall(s.iterate)
+ c.start(self.rate).addErrback(lambda f: f.trap(DrawingFinished)).addErrback(log.err)
+
+ # ITerminalListener
+ def terminalSize(self, width, height):
+ self.width = width
+ self.height = height
+
+ def unhandledControlSequence(self, seq):
+ log.msg("Client sent something weird: %r" % (seq,))
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == '+':
+ self.interval /= 1.1
+ elif keyID == '-':
+ self.interval *= 1.1
+ elif keyID == '*':
+ self.rate /= 1.1
+ elif keyID == '/':
+ self.rate *= 1.1
+ else:
+ log.msg("Client sent: %r" % (keyID,))
+ return
+
+ self._call.stop()
+ self._call = task.LoopingCall(self._iterate)
+ self._call.start(self.interval)
+
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Insults Demo App")
+
+makeService({'protocolFactory': DemoProtocol,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/examples/demo_manhole.tac b/doc/conch/examples/demo_manhole.tac
new file mode 100644
index 0000000..11808c6
--- /dev/null
+++ b/doc/conch/examples/demo_manhole.tac
@@ -0,0 +1,56 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_manhole.tac
+
+"""An interactive Python interpreter with syntax coloring.
+
+Nothing interesting is actually defined here. Two listening ports are
+set up and attached to protocols which know how to properly set up a
+ColoredManhole instance.
+"""
+
+from twisted.conch.manhole import ColoredManhole
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Interactive Python Interpreter")
+
+makeService({'protocolFactory': ColoredManhole,
+ 'protocolArgs': (None,),
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/examples/demo_recvline.tac b/doc/conch/examples/demo_recvline.tac
new file mode 100644
index 0000000..17ec78b
--- /dev/null
+++ b/doc/conch/examples/demo_recvline.tac
@@ -0,0 +1,77 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_recvline.tac
+
+"""Demonstrates line-at-a-time handling with basic line-editing support.
+
+This is a variation on the echo server. It sets up two listening
+ports: one on 6022 which accepts ssh connections; one on 6023 which
+accepts telnet connections. No login for the telnet server is
+required; for the ssh server, \"username\" is the username and
+\"password\" is the password.
+
+The demo protocol defined in this module is handed a line of input at
+a time, which it simply writes back to the connection.
+HistoricRecvline, which the demo protocol subclasses, provides basic
+line editing and input history features.
+"""
+
+from twisted.conch import recvline
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+class DemoRecvLine(recvline.HistoricRecvLine):
+ """Simple echo protocol.
+
+ Accepts lines of input and writes them back to its connection. If
+ a line consisting solely of \"quit\" is received, the connection
+ is dropped.
+ """
+
+ def lineReceived(self, line):
+ if line == "quit":
+ self.terminal.loseConnection()
+ self.terminal.write(line)
+ self.terminal.nextLine()
+ self.terminal.write(self.ps[self.pn])
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Insults RecvLine Demo")
+
+makeService({'protocolFactory': DemoRecvLine,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/examples/demo_scroll.tac b/doc/conch/examples/demo_scroll.tac
new file mode 100644
index 0000000..3e63c09
--- /dev/null
+++ b/doc/conch/examples/demo_scroll.tac
@@ -0,0 +1,100 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_scroll.tac
+
+"""Simple echo-ish server that uses the scroll-region.
+
+This demo sets up two listening ports: one on 6022 which accepts ssh
+connections; one on 6023 which accepts telnet connections. No login
+for the telnet server is required; for the ssh server, \"username\" is
+the username and \"password\" is the password.
+
+The TerminalProtocol subclass defined here sets up a scroll-region occupying
+most of the screen. It positions the cursor at the bottom of the screen and
+then echos back printable input. When return is received, the line is
+copied to the upper area of the screen (scrolling anything older up) and
+clears the input line.
+"""
+
+import string
+
+from twisted.python import log
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+class DemoProtocol(insults.TerminalProtocol):
+ """Copies input to an upwards scrolling region.
+ """
+ width = 80
+ height = 24
+
+ def connectionMade(self):
+ self.buffer = []
+ self.terminalSize(self.width, self.height)
+
+ # ITerminalListener
+ def terminalSize(self, width, height):
+ self.width = width
+ self.height = height
+
+ self.terminal.setScrollRegion(0, height - 1)
+ self.terminal.cursorPosition(0, height)
+ self.terminal.write('> ')
+
+ def unhandledControlSequence(self, seq):
+ log.msg("Client sent something weird: %r" % (seq,))
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == '\r':
+ self.terminal.cursorPosition(0, self.height - 2)
+ self.terminal.nextLine()
+ self.terminal.write(''.join(self.buffer))
+ self.terminal.cursorPosition(0, self.height - 1)
+ self.terminal.eraseToLineEnd()
+ self.terminal.write('> ')
+ self.buffer = []
+ elif keyID in list(string.printable):
+ self.terminal.write(keyID)
+ self.buffer.append(keyID)
+
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Scroll Region Demo App")
+
+makeService({'protocolFactory': DemoProtocol,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/examples/index.html b/doc/conch/examples/index.html
new file mode 100644
index 0000000..cdb7d5e
--- /dev/null
+++ b/doc/conch/examples/index.html
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Conch code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Conch code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">Simple SSH server and client</a></li><li><a href="#auto1">Simple telnet server</a></li><li><a href="#auto2">twisted.conch.insults examples</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Simple SSH server and client<a name="auto0"/></h2>
+ <ul>
+ <li><a href="sshsimpleclient.py" shape="rect">sshsimpleclient.py</a> - simple SSH client</li>
+ <li><a href="sshsimpleserver.py" shape="rect">sshsimpleserver.py</a> - simple SSH server</li>
+ </ul>
+
+ <h2>Simple telnet server<a name="auto1"/></h2>
+ <ul>
+ <li><a href="telnet_echo.tac" shape="rect">telnet_echo.tac</a> - A telnet server which echoes data and events back to the client</li>
+ </ul>
+
+
+ <h2>twisted.conch.insults examples<a name="auto2"/></h2>
+ <ul>
+ <li><a href="demo.tac" shape="rect">demo.tac</a> - Nearly pointless demonstration of the manhole interactive interpreter</li>
+ <li><a href="demo_draw.tac" shape="rect">demo_draw.tac</a> - A trivial drawing application</li>
+ <li><a href="demo_insults.tac" shape="rect">demo_insults.tac</a> - Various simple terminal manipulations using the insults module</li>
+ <li><a href="demo_recvline.tac" shape="rect">demo_recvline.tac</a> - Demonstrates line-at-a-time handling with basic line-editing support</li>
+ <li><a href="demo_scroll.tac" shape="rect">demo_scroll.tac</a> - Simple echo-ish server that uses the scroll-region</li>
+ <li><a href="demo_manhole.tac" shape="rect">demo_manhole.tac</a> - An interactive Python interpreter with syntax coloring</li>
+ <li><a href="window.tac" shape="rect">window.tac</a> - An example of various widgets</li>
+ </ul>
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/conch/examples/sshsimpleclient.py b/doc/conch/examples/sshsimpleclient.py
new file mode 100644
index 0000000..0f7738a
--- /dev/null
+++ b/doc/conch/examples/sshsimpleclient.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.conch.ssh import transport, userauth, connection, common, keys, channel
+from twisted.internet import defer, protocol, reactor
+from twisted.python import log
+import struct, sys, getpass, os
+
+USER = 'z3p' # replace this with a valid username
+HOST = 'localhost' # and a valid host
+
+class SimpleTransport(transport.SSHClientTransport):
+ def verifyHostKey(self, hostKey, fingerprint):
+ print 'host key fingerprint: %s' % fingerprint
+ return defer.succeed(1)
+
+ def connectionSecure(self):
+ self.requestService(
+ SimpleUserAuth(USER,
+ SimpleConnection()))
+
+class SimpleUserAuth(userauth.SSHUserAuthClient):
+ def getPassword(self):
+ return defer.succeed(getpass.getpass("%s@%s's password: " % (USER, HOST)))
+
+ def getGenericAnswers(self, name, instruction, questions):
+ print name
+ print instruction
+ answers = []
+ for prompt, echo in questions:
+ if echo:
+ answer = raw_input(prompt)
+ else:
+ answer = getpass.getpass(prompt)
+ answers.append(answer)
+ return defer.succeed(answers)
+
+ def getPublicKey(self):
+ path = os.path.expanduser('~/.ssh/id_dsa')
+ # this works with rsa too
+ # just change the name here and in getPrivateKey
+ if not os.path.exists(path) or self.lastPublicKey:
+ # the file doesn't exist, or we've tried a public key
+ return
+ return keys.Key.fromFile(filename=path+'.pub').blob()
+
+ def getPrivateKey(self):
+ path = os.path.expanduser('~/.ssh/id_dsa')
+ return defer.succeed(keys.Key.fromFile(path).keyObject)
+
+class SimpleConnection(connection.SSHConnection):
+ def serviceStarted(self):
+ self.openChannel(TrueChannel(2**16, 2**15, self))
+ self.openChannel(FalseChannel(2**16, 2**15, self))
+ self.openChannel(CatChannel(2**16, 2**15, self))
+
+class TrueChannel(channel.SSHChannel):
+ name = 'session' # needed for commands
+
+ def openFailed(self, reason):
+ print 'true failed', reason
+
+ def channelOpen(self, ignoredData):
+ self.conn.sendRequest(self, 'exec', common.NS('true'))
+
+ def request_exit_status(self, data):
+ status = struct.unpack('>L', data)[0]
+ print 'true status was: %s' % status
+ self.loseConnection()
+
+class FalseChannel(channel.SSHChannel):
+ name = 'session'
+
+ def openFailed(self, reason):
+ print 'false failed', reason
+
+ def channelOpen(self, ignoredData):
+ self.conn.sendRequest(self, 'exec', common.NS('false'))
+
+ def request_exit_status(self, data):
+ status = struct.unpack('>L', data)[0]
+ print 'false status was: %s' % status
+ self.loseConnection()
+
+class CatChannel(channel.SSHChannel):
+ name = 'session'
+
+ def openFailed(self, reason):
+ print 'echo failed', reason
+
+ def channelOpen(self, ignoredData):
+ self.data = ''
+ d = self.conn.sendRequest(self, 'exec', common.NS('cat'), wantReply = 1)
+ d.addCallback(self._cbRequest)
+
+ def _cbRequest(self, ignored):
+ self.write('hello conch\n')
+ self.conn.sendEOF(self)
+
+ def dataReceived(self, data):
+ self.data += data
+
+ def closed(self):
+ print 'got data from cat: %s' % repr(self.data)
+ self.loseConnection()
+ reactor.stop()
+
+protocol.ClientCreator(reactor, SimpleTransport).connectTCP(HOST, 22)
+reactor.run()
diff --git a/doc/conch/examples/sshsimpleserver.py b/doc/conch/examples/sshsimpleserver.py
new file mode 100755
index 0000000..5ebcee8
--- /dev/null
+++ b/doc/conch/examples/sshsimpleserver.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.cred import portal, checkers
+from twisted.conch import error, avatar
+from twisted.conch.checkers import SSHPublicKeyDatabase
+from twisted.conch.ssh import factory, userauth, connection, keys, session
+from twisted.internet import reactor, protocol, defer
+from twisted.python import log
+from zope.interface import implements
+import sys
+log.startLogging(sys.stderr)
+
+"""
+Example of running another protocol over an SSH channel.
+log in with username "user" and password "password".
+"""
+
+class ExampleAvatar(avatar.ConchUser):
+
+ def __init__(self, username):
+ avatar.ConchUser.__init__(self)
+ self.username = username
+ self.channelLookup.update({'session':session.SSHSession})
+
+class ExampleRealm:
+ implements(portal.IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ return interfaces[0], ExampleAvatar(avatarId), lambda: None
+
+class EchoProtocol(protocol.Protocol):
+ """this is our example protocol that we will run over SSH
+ """
+ def dataReceived(self, data):
+ if data == '\r':
+ data = '\r\n'
+ elif data == '\x03': #^C
+ self.transport.loseConnection()
+ return
+ self.transport.write(data)
+
+publicKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBEvLi8DVPrJ3/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYLh5KmRpslkYHRivcJSkbh/C+BR3utDS555mV'
+
+privateKey = """-----BEGIN RSA PRIVATE KEY-----
+MIIByAIBAAJhAK8ycfDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW
+4sbUIZR/ZXzY1CMfuC5qyR+UDUbBaaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fw
+vgUd7rQ0ueeZlQIBIwJgbh+1VZfr7WftK5lu7MHtqE1S1vPWZQYE3+VUn8yJADyb
+Z4fsZaCrzW9lkIqXkE3GIY+ojdhZhkO1gbG0118sIgphwSWKRxK0mvh6ERxKqIt1
+xJEJO74EykXZV4oNJ8sjAjEA3J9r2ZghVhGN6V8DnQrTk24Td0E8hU8AcP0FVP+8
+PQm/g/aXf2QQkQT+omdHVEJrAjEAy0pL0EBH6EVS98evDCBtQw22OZT52qXlAwZ2
+gyTriKFVoqjeEjt3SZKKqXHSApP/AjBLpF99zcJJZRq2abgYlf9lv1chkrWqDHUu
+DZttmYJeEfiFBBavVYIF1dOlZT0G8jMCMBc7sOSZodFnAiryP+Qg9otSBjJ3bQML
+pSTqy7c3a2AScC/YyOwkDaICHnnD3XyjMwIxALRzl0tQEKMXs6hH8ToUdlLROCrP
+EhQ0wahUTCk1gKA4uPD6TMTChavbh4K63OvbKg==
+-----END RSA PRIVATE KEY-----"""
+
+
+class InMemoryPublicKeyChecker(SSHPublicKeyDatabase):
+
+ def checkKey(self, credentials):
+ return credentials.username == 'user' and \
+ keys.Key.fromString(data=publicKey).blob() == credentials.blob
+
+class ExampleSession:
+
+ def __init__(self, avatar):
+ """
+ We don't use it, but the adapter is passed the avatar as its first
+ argument.
+ """
+
+ def getPty(self, term, windowSize, attrs):
+ pass
+
+ def execCommand(self, proto, cmd):
+ raise Exception("no executing commands")
+
+ def openShell(self, trans):
+ ep = EchoProtocol()
+ ep.makeConnection(trans)
+ trans.makeConnection(session.wrapProtocol(ep))
+
+ def eofReceived(self):
+ pass
+
+ def closed(self):
+ pass
+
+from twisted.python import components
+components.registerAdapter(ExampleSession, ExampleAvatar, session.ISession)
+
+class ExampleFactory(factory.SSHFactory):
+ publicKeys = {
+ 'ssh-rsa': keys.Key.fromString(data=publicKey)
+ }
+ privateKeys = {
+ 'ssh-rsa': keys.Key.fromString(data=privateKey)
+ }
+ services = {
+ 'ssh-userauth': userauth.SSHUserAuthServer,
+ 'ssh-connection': connection.SSHConnection
+ }
+
+
+portal = portal.Portal(ExampleRealm())
+passwdDB = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+passwdDB.addUser('user', 'password')
+portal.registerChecker(passwdDB)
+portal.registerChecker(InMemoryPublicKeyChecker())
+ExampleFactory.portal = portal
+
+if __name__ == '__main__':
+ reactor.listenTCP(5022, ExampleFactory())
+ reactor.run()
diff --git a/doc/conch/examples/telnet_echo.tac b/doc/conch/examples/telnet_echo.tac
new file mode 100644
index 0000000..daa5b38
--- /dev/null
+++ b/doc/conch/examples/telnet_echo.tac
@@ -0,0 +1,47 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Simple echo server that echoes back client input.
+
+You can run this .tac file directly with:
+ twistd -ny telnet_echo.tac
+
+This demo sets up a listening port on 6023 which accepts telnet connections.
+No login for the telnet server is required.
+"""
+
+from twisted.conch.telnet import TelnetTransport, TelnetProtocol
+from twisted.internet.protocol import ServerFactory
+from twisted.application.internet import TCPServer
+from twisted.application.service import Application
+
+class TelnetEcho(TelnetProtocol):
+ def enableRemote(self, option):
+ self.transport.write("You tried to enable %r (I rejected it)\r\n" % (option,))
+ return False
+
+
+ def disableRemote(self, option):
+ self.transport.write("You disabled %r\r\n" % (option,))
+
+
+ def enableLocal(self, option):
+ self.transport.write("You tried to make me enable %r (I rejected it)\r\n" % (option,))
+ return False
+
+
+ def disableLocal(self, option):
+ self.transport.write("You asked me to disable %r\r\n" % (option,))
+
+
+ def dataReceived(self, data):
+ self.transport.write("I received %r from you\r\n" % (data,))
+
+
+factory = ServerFactory()
+factory.protocol = lambda: TelnetTransport(TelnetEcho)
+service = TCPServer(8023, factory)
+
+application = Application("Telnet Echo Server")
+service.setServiceParent(application)
diff --git a/doc/conch/examples/window.tac b/doc/conch/examples/window.tac
new file mode 100644
index 0000000..5946a48
--- /dev/null
+++ b/doc/conch/examples/window.tac
@@ -0,0 +1,202 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Widgets demo.
+
+You can run this .tac file directly with:
+ twistd -ny window.tac
+
+Demonstrates various widgets or buttons, such as scrollable regions,
+drawable canvas, etc.
+
+This demo sets up two listening ports: one on 6022 which accepts ssh
+connections; one on 6023 which accepts telnet connections. No login for the
+telnet server is required; for the ssh server, "username" is the username and
+"password" is the password.
+"""
+
+from __future__ import division
+
+import string, random
+
+from twisted.python import log
+from twisted.internet import protocol, task
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+from twisted.conch.insults import insults, window
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import reactor
+
+class DrawableCanvas(window.Canvas):
+ x = 0
+ y = 0
+
+ def func_LEFT_ARROW(self, modifier):
+ self.x -= 1
+ self.repaint()
+
+ def func_RIGHT_ARROW(self, modifier):
+ self.x += 1
+ self.repaint()
+
+ def func_UP_ARROW(self, modifier):
+ self.y -= 1
+ self.repaint()
+
+ def func_DOWN_ARROW(self, modifier):
+ self.y += 1
+ self.repaint()
+
+ def characterReceived(self, keyID, modifier):
+ self[self.x, self.y] = keyID
+ self.x += 1
+ self.repaint()
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == '\r' or keyID == '\v':
+ return
+ window.Canvas.keystrokeReceived(self, keyID, modifier)
+ if self.x >= self.width:
+ self.x = 0
+ elif self.x < 0:
+ self.x = self.width - 1
+
+ if self.y >= self.height:
+ self.y = 0
+ elif self.y < 0:
+ self.y = self.height - 1
+ self.repaint()
+
+ def render(self, width, height, terminal):
+ window.Canvas.render(self, width, height, terminal)
+ if self.focused:
+ terminal.cursorPosition(self.x, self.y)
+ window.cursor(terminal, self[self.x, self.y])
+
+
+class ButtonDemo(insults.TerminalProtocol):
+ width = 80
+ height = 24
+
+ def _draw(self):
+ self.window.draw(self.width, self.height, self.terminal)
+
+ def _redraw(self):
+ self.window.filthy()
+ self._draw()
+
+ def _schedule(self, f):
+ reactor.callLater(0, f)
+
+ def connectionMade(self):
+ self.terminal.eraseDisplay()
+ self.terminal.resetPrivateModes([insults.privateModes.CURSOR_MODE])
+
+ self.window = window.TopWindow(self._draw, self._schedule)
+ self.output = window.TextOutput((15, 1))
+ self.input = window.TextInput(15, self._setText)
+ self.select1 = window.Selection(map(str, range(100)), self._setText, 10)
+ self.select2 = window.Selection(map(str, range(200, 300)), self._setText, 10)
+ self.button = window.Button("Clear", self._clear)
+ self.canvas = DrawableCanvas()
+
+ hbox = window.HBox()
+ hbox.addChild(self.input)
+ hbox.addChild(self.output)
+ hbox.addChild(window.Border(self.button))
+ hbox.addChild(window.Border(self.select1))
+ hbox.addChild(window.Border(self.select2))
+
+ t1 = window.TextOutputArea(longLines=window.TextOutputArea.WRAP)
+ t2 = window.TextOutputArea(longLines=window.TextOutputArea.TRUNCATE)
+ t3 = window.TextOutputArea(longLines=window.TextOutputArea.TRUNCATE)
+ t4 = window.TextOutputArea(longLines=window.TextOutputArea.TRUNCATE)
+ for _t in t1, t2, t3, t4:
+ _t.setText((('This is a very long string. ' * 3) + '\n') * 3)
+
+ vp = window.Viewport(t3)
+ d = [1]
+ def spin():
+ vp.xOffset += d[0]
+ if vp.xOffset == 0 or vp.xOffset == 25:
+ d[0] *= -1
+ self.call = task.LoopingCall(spin)
+ self.call.start(0.25, now=False)
+ hbox.addChild(window.Border(vp))
+
+ vp2 = window.ScrolledArea(t4)
+ hbox.addChild(vp2)
+
+ texts = window.VBox()
+ texts.addChild(window.Border(t1))
+ texts.addChild(window.Border(t2))
+
+ areas = window.HBox()
+ areas.addChild(window.Border(self.canvas))
+ areas.addChild(texts)
+
+ vbox = window.VBox()
+ vbox.addChild(hbox)
+ vbox.addChild(areas)
+ self.window.addChild(vbox)
+ self.terminalSize(self.width, self.height)
+
+ def connectionLost(self, reason):
+ self.call.stop()
+ insults.TerminalProtocol.connectionLost(self, reason)
+
+ def terminalSize(self, width, height):
+ self.width = width
+ self.height = height
+ self.terminal.eraseDisplay()
+ self._redraw()
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ self.window.keystrokeReceived(keyID, modifier)
+
+ def _clear(self):
+ self.canvas.clear()
+
+ def _setText(self, text):
+ self.input.setText('')
+ self.output.setText(text)
+
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Window Demo")
+
+makeService({'protocolFactory': ButtonDemo,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/howto/conch_client.html b/doc/conch/howto/conch_client.html
new file mode 100644
index 0000000..cf88a1a
--- /dev/null
+++ b/doc/conch/howto/conch_client.html
@@ -0,0 +1,321 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Writing a client with Twisted Conch</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Writing a client with Twisted Conch</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Writing a client</a></li><li><a href="#auto2">The Transport</a></li><li><a href="#auto3">The Authorization Client</a></li><li><a href="#auto4">The Connection</a></li><li><a href="#auto5">The Channel</a></li><li><a href="#auto6">The main() function</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Introduction<a name="auto0"/></h2>
+
+<p>In the original days of computing, rsh/rlogin were used to connect to
+remote computers and execute commands. These commands had the problem
+that the passwords and commands were sent in the clear. To solve this
+problem, the SSH protocol was created. Twisted Conch implements the
+second version of this protocol.</p>
+
+ <h2>Writing a client<a name="auto1"/></h2>
+
+<p>Writing a client with Conch involves sub-classing 4 classes: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.conch.ssh.transport.SSHClientTransport.html" title="twisted.conch.ssh.transport.SSHClientTransport">twisted.conch.ssh.transport.SSHClientTransport</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.conch.ssh.userauth.SSHUserAuthClient.html" title="twisted.conch.ssh.userauth.SSHUserAuthClient">twisted.conch.ssh.userauth.SSHUserAuthClient</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.conch.ssh.connection.SSHConnection.html" title="twisted.conch.ssh.connection.SSHConnection">twisted.conch.ssh.connection.SSHConnection</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.conch.ssh.channel.SSHChannel.html" title="twisted.conch.ssh.channel.SSHChannel">twisted.conch.ssh.channel.SSHChannel</a></code>. We'll start out
+with <code class="python">SSHClientTransport</code> because it's the base
+of the client.</p>
+
+<h2>The Transport<a name="auto2"/></h2>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">error</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">transport</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientTransport</span>(<span class="py-src-parameter">transport</span>.<span class="py-src-parameter">SSHClientTransport</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">verifyHostKey</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pubKey</span>, <span class="py-src-parameter">fingerprint</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">fingerprint</span> != <span class="py-src-string">'b1:94:6a:c9:24:92:d2:34:7c:62:35:b4:d2:61:11:84'</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">fail</span>(<span class="py-src-variable">error</span>.<span class="py-src-variable">ConchError</span>(<span class="py-src-string">'bad key'</span>))
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-number">1</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionSecure</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">requestService</span>(<span class="py-src-variable">ClientUserAuth</span>(<span class="py-src-string">'user'</span>, <span class="py-src-variable">ClientConnection</span>()))
+</pre>
+
+<p>See how easy it is? <code class="python">SSHClientTransport</code>
+handles the negotiation of encryption and the verification of keys
+for you. The one security element that you as a client writer need to
+implement is <code class="python">verifyHostKey()</code>. This method
+is called with two strings: the public key sent by the server and its
+fingerprint. You should verify the host key the server sends, either
+by checking against a hard-coded value as in the example, or by asking
+the user. <code class="python">verifyHostKey</code> returns a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code> which gets a callback
+if the host key is valid, or an errback if it is not. Note that in the
+above, replace 'user' with the username you're attempting to ssh with,
+for instance a call to <code class="python">os.getlogin()</code> for the
+current user.</p>
+
+<p>The second method you need to implement is <code class="python">connectionSecure()</code>. It is called when the
+encryption is set up and other services can be run. The example requests
+that the <code class="python">ClientUserAuth</code> service be started.
+This service will be discussed next.</p>
+
+<h2>The Authorization Client<a name="auto3"/></h2>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">keys</span>, <span class="py-src-variable">userauth</span>
+
+<span class="py-src-comment"># these are the public/private keys from test_conch</span>
+
+<span class="py-src-variable">publicKey</span> = <span class="py-src-string">'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBEvLi8DVPrJ3\
+/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYLh5KmRpslkYHR\
+ivcJSkbh/C+BR3utDS555mV'</span>
+
+<span class="py-src-variable">privateKey</span> = <span class="py-src-string">&quot;&quot;&quot;-----BEGIN RSA PRIVATE KEY-----
+MIIByAIBAAJhAK8ycfDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW
+4sbUIZR/ZXzY1CMfuC5qyR+UDUbBaaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fw
+vgUd7rQ0ueeZlQIBIwJgbh+1VZfr7WftK5lu7MHtqE1S1vPWZQYE3+VUn8yJADyb
+Z4fsZaCrzW9lkIqXkE3GIY+ojdhZhkO1gbG0118sIgphwSWKRxK0mvh6ERxKqIt1
+xJEJO74EykXZV4oNJ8sjAjEA3J9r2ZghVhGN6V8DnQrTk24Td0E8hU8AcP0FVP+8
+PQm/g/aXf2QQkQT+omdHVEJrAjEAy0pL0EBH6EVS98evDCBtQw22OZT52qXlAwZ2
+gyTriKFVoqjeEjt3SZKKqXHSApP/AjBLpF99zcJJZRq2abgYlf9lv1chkrWqDHUu
+DZttmYJeEfiFBBavVYIF1dOlZT0G8jMCMBc7sOSZodFnAiryP+Qg9otSBjJ3bQML
+pSTqy7c3a2AScC/YyOwkDaICHnnD3XyjMwIxALRzl0tQEKMXs6hH8ToUdlLROCrP
+EhQ0wahUTCk1gKA4uPD6TMTChavbh4K63OvbKg==
+-----END RSA PRIVATE KEY-----&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientUserAuth</span>(<span class="py-src-parameter">userauth</span>.<span class="py-src-parameter">SSHUserAuthClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getPassword</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">prompt</span> = <span class="py-src-parameter">None</span>):
+ <span class="py-src-keyword">return</span>
+ <span class="py-src-comment"># this says we won't do password authentication</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getPublicKey</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">keys</span>.<span class="py-src-variable">Key</span>.<span class="py-src-variable">fromString</span>(<span class="py-src-variable">data</span> = <span class="py-src-variable">publicKey</span>).<span class="py-src-variable">blob</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getPrivateKey</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">keys</span>.<span class="py-src-variable">Key</span>.<span class="py-src-variable">fromString</span>(<span class="py-src-variable">data</span> = <span class="py-src-variable">privateKey</span>).<span class="py-src-variable">keyObject</span>)
+</pre>
+
+<p>Again, fairly simple. The <code class="python">SSHUserAuthClient</code> takes care of most
+of the work, but the actual authentication data needs to be
+supplied. <code class="python">getPassword()</code> asks for a
+password, <code class="python">getPublicKey()</code> and <code class="python">getPrivateKey()</code> get public and private keys,
+respectively. <code class="python">getPassword()</code> returns
+a <code class="python">Deferred</code> that is called back with
+the password to use. <code class="python">getPublicKey()</code>
+returns the SSH key data for the public key to use. <code class="python">keys.Key.fromString()</code> will take
+a key in OpenSSH or LSH format as a string, and convert it to the
+required format. Alternatively, <code class="python">keys.Key.fromFile()</code> can be used instead, which
+will take the filename of a key in OpenSSH and LSH format, and
+convert it to the required format.
+<code class="python">getPrivateKey()</code>
+returns a <code class="python">Deferred</code> which is
+called back with the key object (as used in PyCrypto) for
+the private key. <code class="python">getPassword()</code>
+and <code class="python">getPrivateKey()</code> return <code class="python">Deferreds</code> because they may need to ask the user
+for input.</p>
+
+<p>Once the authentication is complete, <code class="python">SSHUserAuthClient</code> takes care of starting the code
+<code class="python">SSHConnection</code> object given to it. Next, we'll
+look at how to use the <code class="python">SSHConnection</code></p>
+
+<h2>The Connection<a name="auto4"/></h2>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">connection</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientConnection</span>(<span class="py-src-parameter">connection</span>.<span class="py-src-parameter">SSHConnection</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">serviceStarted</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">openChannel</span>(<span class="py-src-variable">CatChannel</span>(<span class="py-src-variable">conn</span> = <span class="py-src-variable">self</span>))
+</pre>
+
+<p><code class="python">SSHConnection</code> is the easiest,
+as it's only responsible for starting the channels. It has
+other methods, those will be examined when we look at <code class="python">SSHChannel</code>.</p>
+
+<h2>The Channel<a name="auto5"/></h2>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">channel</span>, <span class="py-src-variable">common</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CatChannel</span>(<span class="py-src-parameter">channel</span>.<span class="py-src-parameter">SSHChannel</span>):
+
+ <span class="py-src-variable">name</span> = <span class="py-src-string">'session'</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">channelOpen</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">conn</span>.<span class="py-src-variable">sendRequest</span>(<span class="py-src-variable">self</span>, <span class="py-src-string">'exec'</span>, <span class="py-src-variable">common</span>.<span class="py-src-variable">NS</span>(<span class="py-src-string">'cat'</span>),
+ <span class="py-src-variable">wantReply</span> = <span class="py-src-number">1</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cbSendRequest</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span> = <span class="py-src-string">''</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cbSendRequest</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">'This data will be echoed back to us by &quot;cat.&quot;\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">conn</span>.<span class="py-src-variable">sendEOF</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">loseConnection</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span> += <span class="py-src-variable">data</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">closed</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'We got this from &quot;cat&quot;:'</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span>
+</pre>
+
+<p>Now that we've spent all this time getting the server and
+client connected, here is where that work pays off. <code class="python">SSHChannel</code> is the interface between you and the
+other side. This particular channel opens a session and plays with the
+'cat' program, but your channel can implement anything, so long as the
+server supports it.</p>
+
+<p>The <code class="python">channelOpen()</code> method is
+where everything gets started. It gets passed a chunk of data;
+however, this chunk is usually nothing and can be ignored.
+Our <code class="python">channelOpen()</code> initializes our
+channel, and sends a request to the other side, using the
+<code class="python">sendRequest()</code> method of the <code class="python">SSHConnection</code> object. Requests are used to send
+events to the other side. We pass the method self so that it knows to
+send the request for this channel. The 2nd argument of 'exec' tells the
+server that we want to execute a command. The third argument is the data
+that accompanies the request.
+<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.conch.ssh.common.NS.html" title="twisted.conch.ssh.common.NS">common.NS</a></code> encodes
+the data as a length-prefixed string, which is how the server expects
+the data. We also say that we want a reply saying that the process has a
+been started. <code class="python">sendRequest()</code> then returns a
+<code class="python">Deferred</code> which we add a callback for.</p>
+
+<p>Once the callback fires, we send the data. <code class="python">SSHChannel</code> supports the
+<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.ITransport.html" title="twisted.internet.interfaces.ITransport">twisted.internet.interfaces.ITransport</a></code>
+interface, so
+it can be given to Protocols to run them over the secure
+connection. In our case, we just write the data directly. <code class="python">sendEOF()</code> does not follow the interface,
+but Conch uses it to tell the other side that we will write no
+more data. <code class="python">loseConnection()</code> shuts
+down our side of the connection, but we will still receive data
+through <code class="python">dataReceived()</code>. The <code class="python">closed()</code> method is called when both sides of the
+connection are closed, and we use it to display the data we received
+(which should be the same as the data we sent.)</p>
+
+<p>Finally, let's actually invoke the code we've set up.</p>
+
+<h2>The main() function<a name="auto6"/></h2>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ClientFactory</span>()
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">ClientTransport</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">22</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">main</span>()
+</pre>
+
+<P>We call <code class="python">connectTCP()</code> to connect to
+localhost, port 22 (the standard port for ssh), and pass it an instance
+of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.ClientFactory.html" title="twisted.internet.protocol.ClientFactory">twisted.internet.protocol.ClientFactory</a></code>.
+This instance has the attribute <code class="python">protocol</code>
+set to our earlier <code class="python">ClientTransport</code>
+class. Note that the protocol attribute is set to the class <code class="python">ClientTransport</code>, not an instance of
+<code class="python">ClientTransport</code>! When the <code class="python">connectTCP</code> call completes, the protocol will be
+called to create a <code class="python">ClientTransport()</code> object
+- this then invokes all our previous work.</P>
+
+<P>It's worth noting that in the example <code class="python">main()</code>
+routine, the <code class="python">reactor.run()</code> call never returns.
+If you want to make the program exit, call
+<code class="python">reactor.stop()</code> in the earlier
+<code class="python">closed()</code> method.</P>
+
+<P>If you wish to observe the interactions in more detail, adding a call
+to <code class="python">log.startLogging(sys.stdout, setStdout=0)</code>
+before the <code class="python">reactor.run()</code> call will send all
+logging to stdout.</P>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/conch/howto/index.html b/doc/conch/howto/index.html
new file mode 100644
index 0000000..0782a48
--- /dev/null
+++ b/doc/conch/howto/index.html
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+ <li>Tutorial
+ <ul>
+ <li>
+ <a href="conch_client.html" shape="rect">Writing an SSH client with Conch</a>
+ </li>
+ </ul>
+ </li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/conch/index.html b/doc/conch/index.html
new file mode 100644
index 0000000..9416896
--- /dev/null
+++ b/doc/conch/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Conch Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Conch Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Conch to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Conch</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/conch/man/cftp-man.html b/doc/conch/man/cftp-man.html
new file mode 100644
index 0000000..5c20c00
--- /dev/null
+++ b/doc/conch/man/cftp-man.html
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: CFTP.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">CFTP.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>cftp </p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p>cftp [<strong>-B</strong><em> buffer_size</em>][<strong>-b</strong><em> command_file</em>][<strong>-R</strong><em> num_requests</em>][<strong>-s</strong><em> subsystem</em>]</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>cftp is a client for logging into a remote machine and executing commands to send and receive file information. It can wrap a number of file transfer subsystems
+</p>
+
+<p>The options are as follows:
+<dl><dt><strong>-B</strong></dt><dd>Specifies the default size of the buffer to use for sending and receiving. (Default value: 32768 bytes.)
+</dd><dt><strong>-b</strong></dt><dd>File to read commands from, '-' for stdin. (Default value: interactive/stdin.)
+</dd><dt><strong>-R</strong></dt><dd>Number of requests to make before waiting for a reply.
+</dd><dt><strong>-s</strong></dt><dd>Subsystem/server program to connect to.
+</dd></dl>
+
+</p>
+
+<p>The following commands are recognised by
+cftp :
+<dl><dt>cd <u>path</u></dt><dd>Change the remote directory to 'path'.
+</dd><dt>chgrp <u>gid</u> <u>path</u></dt><dd>Change the gid of 'path' to 'gid'.
+</dd><dt>chmod <u>mode</u> <u>path</u></dt><dd>Change mode of 'path' to 'mode'.
+</dd><dt>chown <u>uid</u> <u>path</u></dt><dd>Change uid of 'path' to 'uid'.
+</dd><dt>exit</dt><dd>Disconnect from the server.
+</dd><dt>get <u>remote-path</u> [<u>local-path</u>]</dt><dd>Get remote file and optionally store it at specified local path.
+</dd><dt>help</dt><dd>Get a list of available commands.
+</dd><dt>lcd <u>path</u></dt><dd>Change local directory to 'path'.
+</dd><dt>lls [<u>ls-options</u>] [<u>path</u>]</dt><dd>Display local directory listing.
+</dd><dt>lmkdir <u>path</u></dt><dd>Create local directory.
+</dd><dt>ln <u>linkpath</u> <u>targetpath</u></dt><dd>Symlink remote file.
+</dd><dt>lpwd</dt><dd>Print the local working directory.
+</dd><dt>ls [<u>-l</u>] [<u>path</u>]</dt><dd>Display remote directory listing.
+</dd><dt>mkdir <u>path</u></dt><dd>Create remote directory.
+</dd><dt>progress</dt><dd>Toggle progress bar.
+</dd><dt>put <u>local-path</u> [<u>remote-path</u>]</dt><dd>Transfer local file to remote location
+</dd><dt>pwd</dt><dd>Print the remote working directory.
+</dd><dt>quit</dt><dd>Disconnect from the server.
+</dd><dt>rename <u>oldpath</u> <u>newpath</u></dt><dd>Rename remote file.
+</dd><dt>rmdir <u>path</u></dt><dd>Remove remote directory.
+</dd><dt>rm <u>path</u></dt><dd>Remove remote file.
+</dd><dt>version</dt><dd>Print the SFTP version.
+</dd><dt>?</dt><dd>Synonym for 'help'.
+</dd></dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>cftp by Paul Swartz &lt;z3p@twistedmatrix.com&gt;. Man page by Mary Gardiner &lt;mary@twistedmatrix.com&gt;.
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>Report bugs to <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2005-2008 Twisted Matrix Laboratories
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/conch/man/cftp.1 b/doc/conch/man/cftp.1
new file mode 100644
index 0000000..7eae889
--- /dev/null
+++ b/doc/conch/man/cftp.1
@@ -0,0 +1,89 @@
+.Dd October 8, 2005
+.Dt CFTP 1
+.Os
+.Sh NAME
+.Nm cftp
+.Nd Conch command-line SFTP client
+.Sh SYNOPSIS
+.Nm cftp
+.Op Fl B Ar buffer_size
+.Op Fl b Ar command_file
+.Op Fl R Ar num_requests
+.Op Fl s Ar subsystem
+.Os
+.Sh DESCRIPTION
+.Nm
+is a client for logging into a remote machine and executing commands to send and receive file information. It can wrap a number of file transfer subsystems
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B
+Specifies the default size of the buffer to use for sending and receiving. (Default value: 32768 bytes.)
+.It Fl b
+File to read commands from, '-' for stdin. (Default value: interactive/stdin.)
+.It Fl R
+Number of requests to make before waiting for a reply.
+.It Fl s
+Subsystem/server program to connect to.
+.El
+.Pp
+The following commands are recognised by
+.Nm
+:
+.Bl -tag -width Ds
+.It Ic cd Ar path
+Change the remote directory to 'path'.
+.It Ic chgrp Ar gid Ar path
+Change the gid of 'path' to 'gid'.
+.It Ic chmod Ar mode Ar path
+Change mode of 'path' to 'mode'.
+.It Ic chown Ar uid Ar path
+Change uid of 'path' to 'uid'.
+.It Ic exit
+Disconnect from the server.
+.It Ic get Ar remote-path Op Ar local-path
+Get remote file and optionally store it at specified local path.
+.It Ic help
+Get a list of available commands.
+.It Ic lcd Ar path
+Change local directory to 'path'.
+.It Ic lls Op Ar ls-options Op Ar path
+Display local directory listing.
+.It Ic lmkdir Ar path
+Create local directory.
+.It Ic ln Ar linkpath Ar targetpath
+Symlink remote file.
+.It Ic lpwd
+Print the local working directory.
+.It Ic ls Op Ar -l Op Ar path
+Display remote directory listing.
+.It Ic mkdir Ar path
+Create remote directory.
+.It Ic progress
+Toggle progress bar.
+.It Ic put Ar local-path Op Ar remote-path
+Transfer local file to remote location
+.It Ic pwd
+Print the remote working directory.
+.It Ic quit
+Disconnect from the server.
+.It Ic rename Ar oldpath Ar newpath
+Rename remote file.
+.It Ic rmdir Ar path
+Remove remote directory.
+.It Ic rm Ar path
+Remove remote file.
+.It Ic version
+Print the SFTP version.
+.It Ic ?
+Synonym for 'help'.
+.El
+.Sh AUTHOR
+cftp by Paul Swartz <z3p@twistedmatrix.com>. Man page by Mary Gardiner <mary@twistedmatrix.com>.
+.Sh "REPORTING BUGS"
+Report bugs to \fIhttp://twistedmatrix.com/bugs/\fR
+.Sh COPYRIGHT
+Copyright \(co 2005-2008 Twisted Matrix Laboratories
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/conch/man/ckeygen-man.html b/doc/conch/man/ckeygen-man.html
new file mode 100644
index 0000000..e16fe32
--- /dev/null
+++ b/doc/conch/man/ckeygen-man.html
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: CKEYGEN.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">CKEYGEN.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">DESCRIPTION</a></li><li><a href="#auto4">AUTHOR</a></li><li><a href="#auto5">REPORTING BUGS</a></li><li><a href="#auto6">COPYRIGHT</a></li><li><a href="#auto7">SEE ALSO</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>ckeygen - manipulate public/private keys
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>ckeygen</strong> [-b <em>bits</em>] [-f <em>filename</em>] [-t <em>type</em>]<strong>[-C</strong> <em>comment</em>] [-N <em>new passphrase</em>] [-P <em>old passphrase</em>]<strong>[-l]</strong> [-p] [-q] [-y]</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>The <strong>--help</strong> prints out a usage message to standard output.
+<dl><dt><strong>-b</strong>, <strong>--bits</strong> &lt;bits&gt;
+</dt><dd>Number of bits in the key to create (default: 1024)
+</dd>
+
+<dt><strong>-f</strong>, <strong>--filename</strong> &lt;file name&gt;
+</dt><dd>Filename of the key file.
+</dd>
+
+<dt><strong>-t</strong>, <strong>--type</strong> &lt;type&gt;
+</dt><dd>Type of key (rsa or dsa).
+</dd>
+
+<dt><strong>-C</strong>, <strong>--comment</strong> &lt;comment&gt;
+</dt><dd>Provide a new comment.
+</dd>
+
+<dt><strong>-N</strong>, <strong>--newpass</strong> &lt;pass phrase&gt;
+</dt><dd>Provide new passphrase.
+</dd>
+
+<dt><strong>-P</strong>, <strong>--pass</strong> &lt;pass phrase&gt;
+</dt><dd>Provide old passphrase.
+</dd>
+
+<dt><strong>-l</strong>, <strong>--fingerprint</strong>
+</dt><dd>Show fingerprint of key file.
+</dd>
+
+<dt><strong>-p</strong>, <strong>--changepass</strong>
+</dt><dd>Change passphrase of private key file.
+</dd>
+
+<dt><strong>-q</strong>, <strong>--quiet</strong>
+</dt><dd>Be quiet.
+</dd>
+
+<dt><strong>-y</strong>, <strong>--showpub</strong>
+</dt><dd>Read private key file and print public key.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Display version number only.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>DESCRIPTION<a name="auto3"/></h2>
+
+<p>Manipulate public/private keys in various ways.
+If no filename is given, a file name will be requested interactively.
+</p>
+
+<h2>AUTHOR<a name="auto4"/></h2>
+
+<p>Written by Moshe Zadka, based on ckeygen's help messages
+</p>
+
+<h2>REPORTING BUGS<a name="auto5"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto6"/></h2>
+
+<p>Copyright © 2002-2011 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+<h2>SEE ALSO<a name="auto7"/></h2>
+
+<p>ssh(1), conch(1)
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/conch/man/ckeygen.1 b/doc/conch/man/ckeygen.1
new file mode 100644
index 0000000..04d720f
--- /dev/null
+++ b/doc/conch/man/ckeygen.1
@@ -0,0 +1,57 @@
+.TH CKEYGEN "1" "October 2002" "" ""
+.SH NAME
+ckeygen \- manipulate public/private keys
+.SH SYNOPSIS
+.B ckeygen [-b \fIbits\fR] [-f \fIfilename\fR] [-t \fItype\fR]
+.B [-C \fIcomment\fR] [-N \fInew passphrase\fR] [-P \fIold passphrase\fR]
+.B [-l] [-p] [-q] [-y]
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB-b\fR, \fB--bits\fR <bits>
+Number of bits in the key to create (default: 1024)
+.TP
+\fB-f\fR, \fB--filename\fR <file name>
+Filename of the key file.
+.TP
+\fB-t\fR, \fB--type\fR <type>
+Type of key (rsa or dsa).
+.TP
+\fB-C\fR, \fB--comment\fR <comment>
+Provide a new comment.
+.TP
+\fB-N\fR, \fB--newpass\fR <pass phrase>
+Provide new passphrase.
+.TP
+\fB-P\fR, \fB--pass\fR <pass phrase>
+Provide old passphrase.
+.TP
+\fB-l\fR, \fB--fingerprint\fR
+Show fingerprint of key file.
+.TP
+\fB-p\fR, \fB--changepass\fR
+Change passphrase of private key file.
+.TP
+\fB-q\fR, \fB--quiet\fR
+Be quiet.
+.TP
+\fB-y\fR, \fB--showpub\fR
+Read private key file and print public key.
+.TP
+\fB--version\fR
+Display version number only.
+.SH DESCRIPTION
+Manipulate public/private keys in various ways.
+If no filename is given, a file name will be requested interactively.
+.SH AUTHOR
+Written by Moshe Zadka, based on ckeygen's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2002-2011 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+ssh(1), conch(1)
diff --git a/doc/conch/man/conch-man.html b/doc/conch/man/conch-man.html
new file mode 100644
index 0000000..abf5a98
--- /dev/null
+++ b/doc/conch/man/conch-man.html
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: CONCH.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">CONCH.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li><li><a href="#auto6">SEE ALSO</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>conch </p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p>conch [<strong>-AaCfINnrsTtVvx</strong>][<strong>-c</strong><em> cipher_spec</em>][<strong>-e</strong><em> escape_char</em>][<strong>-i</strong><em> identity_file</em>][<strong>-K</strong><em> connection_spec</em>][<strong>-L</strong><em> port</em>:<em> host</em>:<em> hostport</em>][<strong>-l</strong><em> user</em>][<strong>-m</strong><em> mac_spec</em>][<strong>-o</strong><em> openssh_option</em>][<strong>-p</strong><em> port</em>][<strong>-R</strong><em> port</em>:<em> host</em>:<em> hostport</em>][<em> user</em>@]<em> hostname</em>[<em> command</em>]</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>conch is a SSHv2 client for logging into a remote machine and executing commands. It provides encrypted and secure communications across a possibly insecure network. Arbitrary TCP/IP ports can also be forwarded over the secure connection.
+</p>
+
+<p>conch connects and logs into
+<em> hostname</em>(as
+<em> user</em>or the current username). The user must prove her/his identity through a public-key or a password. Alternatively, if a connection is already open to a server, a new shell can be opened over the connection without having to reauthenticate.
+</p>
+
+<p>If
+<em> command</em>is specified,
+<em> command</em>is executed instead of a shell. If the
+<strong>-s</strong>option is given,
+<em> command</em>is treated as an SSHv2 subsystem name.
+Conch supports the public-key, keyboard-interactive, and password authentications.
+</p>
+
+<p>The public-key method allows the RSA or DSA algorithm to be used. The client uses his/her private key,
+or
+to sign the session identifier, known only by the client and server. The server checks that the matching public key is valid for the user, and that the signature is correct.
+</p>
+
+<p>If public-key authentication fails,
+conch can authenticate by sending an encrypted password over the connection.
+conch has the ability to multiplex multiple shells, commands and TCP/IP ports over the same secure connection. To disable multiplexing for a connection, use the
+<strong>-I</strong>flag.
+</p>
+
+<p>The
+<strong>-K</strong>option determines how the client connects to the remote host. It is a comma-separated list of the methods to use, in order of preference. The two connection methods are
+(for connecting over a multiplexed connection) and
+(to connect directly).
+To disable connecting over a multiplexed connection, do not include
+in the preference list.
+</p>
+
+<p>As an example of how connection sharing works, to speed up CVS over SSH:
+</p>
+
+<p>conch --noshell --fork -l cvs_user cvs_host
+set CVS_RSH=<strong>conch</strong>
+</p>
+
+<p>Now, when CVS connects to cvs_host as cvs_user, instead of making a new connection to the server,
+conch will add a new channel to the existing connection. This saves the cost of repeatedly negotiating the cryptography and authentication.
+</p>
+
+<p>The options are as follows:
+<dl><dt><strong>-A</strong></dt><dd>Enables authentication agent forwarding.
+</dd><dt><strong>-a</strong></dt><dd>Disables authentication agent forwarding (default).
+</dd><dt><strong>-C</strong></dt><dd>Enable compression.
+</dd><dt><strong>-c</strong></dt><dd><em> cipher_spec</em>Selects encryption algorithms to be used for this connection, as a comma-separated list of ciphers in order of preference. The list that
+conch supports is (in order of default preference): aes256-ctr, aes256-cbc, aes192-ctr, aes192-cbc, aes128-ctr, aes128-cbc, cast128-ctr, cast128-cbc, blowfish-ctr, blowfish, idea-ctr, idea-cbc, 3des-ctr, 3des-cbc.
+</dd><dt><strong>-e</strong></dt><dd><em> ch</em>| ^ch | noneSets the escape character for sessions with a PTY (default:
+The escape character is only recognized at the beginning of a line (after a newline).
+The escape character followed by a dot
+closes the connection;
+followed by ^Z suspends the connection;
+and followed by the escape character sends the escape character once.
+Setting the character to
+disables any escapes.
+</dd><dt><strong>-f</strong></dt><dd>Fork to background after authentication.
+</dd><dt><strong>-I</strong></dt><dd>Do not allow connection sharing over this connection.
+</dd><dt><strong>-i</strong></dt><dd><em> identity_spec</em>The file from which the identity (private key) for RSA or DSA authentication is read.
+The defaults are
+and
+It is possible to use this option more than once to use more than one private key.
+</dd><dt><strong>-K</strong></dt><dd><em> connection_spec</em>Selects methods for connection to the server, as a comma-separated list of methods in order of preference. See
+for more information.
+</dd><dt><strong>-L</strong></dt><dd><em> port</em>: host : hostportSpecifies that the given port on the client host is to be forwarded to the given host and port on the remote side. This allocates a socket to listen to
+<em> port</em>on the local side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+<em> host</em>port
+<em> hostport</em>from the remote machine.
+Only root can forward privieged ports.
+</dd><dt><strong>-l</strong></dt><dd><em> user</em>Log in using this username.
+</dd><dt><strong>-m</strong></dt><dd><em> mac_spec</em>Selects MAC (message authentication code) algorithms, as a comma-separated list in order of preference. The list that
+conch supports is (in order of preference): hmac-sha1, hmac-md5.
+</dd><dt><strong>-N</strong></dt><dd>Do not execute a shell or command.
+</dd><dt><strong>-n</strong></dt><dd>Redirect input from /dev/null.
+</dd><dt><strong>-o</strong></dt><dd><em> openssh_option</em>Ignored OpenSSH options.
+</dd><dt><strong>-p</strong></dt><dd><em> port</em>The port to connect to on the server.
+</dd><dt><strong>-R</strong></dt><dd><em> port</em>: host : hostportSpecifies that the given port on the remote host is to be forwarded to the given host and port on the local side. This allocates a socket to listen to
+<em> port</em>on the remote side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+<em> host</em>port
+<em> hostport</em>from the client host.
+Only root can forward privieged ports.
+</dd><dt><strong>-s</strong></dt><dd>Reconnect to the server if the connection is lost.
+</dd><dt><strong>-s</strong></dt><dd>Invoke
+<em> command</em>(mandatory) as a SSHv2 subsystem.
+</dd><dt><strong>-T</strong></dt><dd>Do not allocate a TTY.
+</dd><dt><strong>-t</strong></dt><dd>Allocate a TTY even if command is given.
+</dd><dt><strong>-V</strong></dt><dd>Display version number only.
+</dd><dt><strong>-v</strong></dt><dd>Log to stderr.
+</dd><dt><strong>-x</strong></dt><dd>Disable X11 connection forwarding (default).
+</dd></dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Paul Swartz &lt;z3p@twistedmatrix.com&gt;.
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2002-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+<h2>SEE ALSO<a name="auto6"/></h2>
+
+<p>ssh(1)
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/conch/man/conch.1 b/doc/conch/man/conch.1
new file mode 100644
index 0000000..7ba9bff
--- /dev/null
+++ b/doc/conch/man/conch.1
@@ -0,0 +1,206 @@
+.Dd May 22, 2004
+.Dt CONCH 1
+.Os
+.Sh NAME
+.Nm conch
+.Nd Conch SSH client
+.Sh SYNOPSIS
+.Nm conch
+.Op Fl AaCfINnrsTtVvx
+.Op Fl c Ar cipher_spec
+.Op Fl e Ar escape_char
+.Op Fl i Ar identity_file
+.Op Fl K Ar connection_spec
+.Bk -words
+.Oo Fl L Xo
+.Sm off
+.Ar port :
+.Ar host :
+.Ar hostport
+.Sm on
+.Xc
+.Oc
+.Ek
+.Op Fl l Ar user
+.Op Fl m Ar mac_spec
+.Op Fl o Ar openssh_option
+.Op Fl p Ar port
+.Bk -words
+.Oo Fl R Xo
+.Sm off
+.Ar port :
+.Ar host :
+.Ar hostport
+.Sm on
+.Xc
+.Oc
+.Ek
+.Oo Ar user Ns @ Ns Oc Ar hostname
+.Op Ar command
+.Sh DESCRIPTION
+.Nm
+is a SSHv2 client for logging into a remote machine and executing commands. It provides encrypted and secure communications across a possibly insecure network. Arbitrary TCP/IP ports can also be forwarded over the secure connection.
+.Pp
+.Nm
+connects and logs into
+.Ar hostname
+(as
+.Ar user
+or the current username). The user must prove her/his identity through a public\-key or a password. Alternatively, if a connection is already open to a server, a new shell can be opened over the connection without having to reauthenticate.
+.Pp
+If
+.Ar command
+is specified,
+.Ar command
+is executed instead of a shell. If the
+.Fl s
+option is given,
+.Ar command
+is treated as an SSHv2 subsystem name.
+.Ss Authentication
+Conch supports the public-key, keyboard-interactive, and password authentications.
+.Pp
+The public-key method allows the RSA or DSA algorithm to be used. The client uses his/her private key,
+.Pa $HOME/.ssh/id_rsa
+or
+.Pa $HOME/.ssh/id_dsa
+to sign the session identifier, known only by the client and server. The server checks that the matching public key is valid for the user, and that the signature is correct.
+.Pp
+If public-key authentication fails,
+.Nm
+can authenticate by sending an encrypted password over the connection.
+.Ss Connection sharing
+.Nm
+has the ability to multiplex multiple shells, commands and TCP/IP ports over the same secure connection. To disable multiplexing for a connection, use the
+.Fl I
+flag.
+.Pp
+The
+.Fl K
+option determines how the client connects to the remote host. It is a comma-separated list of the methods to use, in order of preference. The two connection methods are
+.Ql unix
+(for connecting over a multiplexed connection) and
+.Ql direct
+(to connect directly).
+To disable connecting over a multiplexed connection, do not include
+.Ql unix
+in the preference list.
+.Pp
+As an example of how connection sharing works, to speed up CVS over SSH:
+.Pp
+.Nm
+--noshell --fork -l cvs_user cvs_host
+.br
+set CVS_RSH=\fBconch\fR
+.Pp
+Now, when CVS connects to cvs_host as cvs_user, instead of making a new connection to the server,
+.Nm
+will add a new channel to the existing connection. This saves the cost of repeatedly negotiating the cryptography and authentication.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl A
+Enables authentication agent forwarding.
+.It Fl a
+Disables authentication agent forwarding (default).
+.It Fl C
+Enable compression.
+.It Fl c Ar cipher_spec
+Selects encryption algorithms to be used for this connection, as a comma-separated list of ciphers in order of preference. The list that
+.Nm
+supports is (in order of default preference): aes256-ctr, aes256-cbc, aes192-ctr, aes192-cbc, aes128-ctr, aes128-cbc, cast128-ctr, cast128-cbc, blowfish-ctr, blowfish, idea-ctr, idea-cbc, 3des-ctr, 3des-cbc.
+.It Fl e Ar ch | ^ch | none
+Sets the escape character for sessions with a PTY (default:
+.Ql ~ ) .
+The escape character is only recognized at the beginning of a line (after a newline).
+The escape character followed by a dot
+.Pq Ql \&.
+closes the connection;
+followed by ^Z suspends the connection;
+and followed by the escape character sends the escape character once.
+Setting the character to
+.Dq none
+disables any escapes.
+.It Fl f
+Fork to background after authentication.
+.It Fl I
+Do not allow connection sharing over this connection.
+.It Fl i Ar identity_spec
+The file from which the identity (private key) for RSA or DSA authentication is read.
+The defaults are
+.Pa $HOME/.ssh/id_rsa
+and
+.Pa $HOME/.ssh/id_dsa .
+It is possible to use this option more than once to use more than one private key.
+.It Fl K Ar connection_spec
+Selects methods for connection to the server, as a comma-separated list of methods in order of preference. See
+.Cm Connection sharing
+for more information.
+.It Fl L Xo
+.Sm off
+.Ar port : host : hostport
+.Sm on
+.Xc
+Specifies that the given port on the client host is to be forwarded to the given host and port on the remote side. This allocates a socket to listen to
+.Ar port
+on the local side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+.Ar host
+port
+.Ar hostport
+from the remote machine.
+Only root can forward privieged ports.
+.It Fl l Ar user
+Log in using this username.
+.It Fl m Ar mac_spec
+Selects MAC (message authentication code) algorithms, as a comma-separated list in order of preference. The list that
+.Nm
+supports is (in order of preference): hmac-sha1, hmac-md5.
+.It Fl N
+Do not execute a shell or command.
+.It Fl n
+Redirect input from /dev/null.
+.It Fl o Ar openssh_option
+Ignored OpenSSH options.
+.It Fl p Ar port
+The port to connect to on the server.
+.It Fl R Xo
+.Sm off
+.Ar port : host : hostport
+.Sm on
+.Xc
+Specifies that the given port on the remote host is to be forwarded to the given host and port on the local side. This allocates a socket to listen to
+.Ar port
+on the remote side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+.Ar host
+port
+.Ar hostport
+from the client host.
+Only root can forward privieged ports.
+.It Fl s
+Reconnect to the server if the connection is lost.
+.It Fl s
+Invoke
+.Ar command
+(mandatory) as a SSHv2 subsystem.
+.It Fl T
+Do not allocate a TTY.
+.It Fl t
+Allocate a TTY even if command is given.
+.It Fl V
+Display version number only.
+.It Fl v
+Log to stderr.
+.It Fl x
+Disable X11 connection forwarding (default).
+.El
+.Sh AUTHOR
+Written by Paul Swartz <z3p@twistedmatrix.com>.
+.Sh "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.Sh COPYRIGHT
+Copyright \(co 2002-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.Sh SEE ALSO
+ssh(1)
diff --git a/doc/conch/man/tkconch-man.html b/doc/conch/man/tkconch-man.html
new file mode 100644
index 0000000..41ae30a
--- /dev/null
+++ b/doc/conch/man/tkconch-man.html
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: CONCH.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">CONCH.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">DESCRIPTION</a></li><li><a href="#auto4">AUTHOR</a></li><li><a href="#auto5">REPORTING BUGS</a></li><li><a href="#auto6">COPYRIGHT</a></li><li><a href="#auto7">SEE ALSO</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>tkconch - connect to SSH servers graphically
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>conch</strong> [-l <em>user</em>] [-i <em>identity</em> [ -i <em>identity</em> ... ]] [-c <em>cipher</em>] [-m <em>MAC</em>] [-p <em>port</em>] [-n] [-t] [-T] [-V] [-C] [-N] [-s] [arg [...]]</p>
+
+<p><strong>conch</strong> --help</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>The <strong>--help</strong> prints out a usage message to standard output.
+<dl><dt><strong>-l</strong>, <strong>--user</strong> &lt;user&gt;
+</dt><dd>Log in using this user name.
+</dd>
+
+<dt><strong>-e</strong>, <strong>--escape</strong> &lt;escape character&gt;
+</dt><dd>Set escape character; 'none' = disable (default: ~)
+</dd>
+
+<dt><strong>-i</strong>, <strong>--identity</strong> &lt;identity&gt;
+</dt><dd>Add an identity file for public key authentication (default: ~/.ssh/identity)
+</dd>
+
+<dt><strong>-c</strong>, <strong>--cipher</strong> &lt;cipher&gt;
+</dt><dd>Cipher algorithm to use.
+</dd>
+
+<dt><strong>-m</strong>, <strong>--macs</strong> &lt;mac&gt;
+</dt><dd>Specify MAC algorithms for protocol version 2.
+</dd>
+
+<dt><strong>-p</strong>, <strong>--port</strong> &lt;port&gt;
+</dt><dd>Port to connect to.
+</dd>
+
+<dt><strong>-L</strong>, <strong>--localforward</strong> &lt;listen-port:host:port&gt;
+</dt><dd>Forward local port to remote address.
+</dd>
+
+<dt><strong>-R</strong>, <strong>--remoteforward</strong> &lt;listen-port:host:port&gt;
+</dt><dd>Forward remote port to local address.
+</dd>
+
+<dt><strong>-t</strong>, <strong>--tty</strong>
+</dt><dd>Allocate a tty even if command is given.
+</dd>
+
+<dt><strong>-n</strong>, <strong>--notty</strong>
+</dt><dd>Do not allocate a tty.
+</dd>
+
+<dt><strong>-V</strong>, <strong>--version</strong>
+</dt><dd>Display version number only.
+</dd>
+
+<dt><strong>-C</strong>, <strong>--compress</strong>
+</dt><dd>Enable compression.
+</dd>
+
+<dt><strong>-a</strong>, <strong>--ansilog</strong>
+</dt><dd>Print the received data to stdout.
+</dd>
+
+<dt><strong>-N</strong>, <strong>--noshell</strong>
+</dt><dd>Do not execute a shell or command.
+</dd>
+
+<dt><strong>-s</strong>, <strong>--subsystem</strong>
+</dt><dd>Invoke command (mandatory) as SSH2 subsystem.
+</dd>
+
+<dt><strong>--log</strong>
+</dt><dd>Print the receieved data to stderr.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>DESCRIPTION<a name="auto3"/></h2>
+
+<p>Open an SSH connection to specified server, and either run the command
+given there or open a remote interactive shell.
+</p>
+
+<h2>AUTHOR<a name="auto4"/></h2>
+
+<p>Written by Moshe Zadka, based on conch's help messages
+</p>
+
+<h2>REPORTING BUGS<a name="auto5"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto6"/></h2>
+
+<p>Copyright © 2002-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+<h2>SEE ALSO<a name="auto7"/></h2>
+
+<p>ssh(1)
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/conch/man/tkconch.1 b/doc/conch/man/tkconch.1
new file mode 100644
index 0000000..54260bf
--- /dev/null
+++ b/doc/conch/man/tkconch.1
@@ -0,0 +1,72 @@
+.TH CONCH "1" "October 2002" "" ""
+.SH NAME
+tkconch \- connect to SSH servers graphically
+.SH SYNOPSIS
+.B conch [-l \fIuser\fR] [-i \fIidentity\fR [ -i \fIidentity\fR ... ]] [-c \fIcipher\fR] [-m \fIMAC\fR] [-p \fIport\fR] [-n] [-t] [-T] [-V] [-C] [-N] [-s] [arg [...]]
+.PP
+.B conch --help
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB-l\fR, \fB--user\fR <user>
+Log in using this user name.
+.TP
+\fB-e\fR, \fB--escape\fR <escape character>
+Set escape character; 'none' = disable (default: ~)
+.TP
+\fB-i\fR, \fB--identity\fR <identity>
+Add an identity file for public key authentication (default: ~/.ssh/identity)
+.TP
+\fB-c\fR, \fB--cipher\fR <cipher>
+Cipher algorithm to use.
+.TP
+\fB-m\fR, \fB--macs\fR <mac>
+Specify MAC algorithms for protocol version 2.
+.TP
+\fB-p\fR, \fB--port\fR <port>
+Port to connect to.
+.TP
+\fB-L\fR, \fB--localforward\fR <listen-port:host:port>
+Forward local port to remote address.
+.TP
+\fB-R\fR, \fB--remoteforward\fR <listen-port:host:port>
+Forward remote port to local address.
+.TP
+\fB-t\fR, \fB--tty\fR
+Allocate a tty even if command is given.
+.TP
+\fB-n\fR, \fB--notty\fR
+Do not allocate a tty.
+.TP
+\fB-V\fR, \fB--version\fR
+Display version number only.
+.TP
+\fB-C\fR, \fB--compress\fR
+Enable compression.
+.TP
+\fB-a\fR, \fB--ansilog\fR
+Print the received data to stdout.
+.TP
+\fB-N\fR, \fB--noshell\fR
+Do not execute a shell or command.
+.TP
+\fB-s\fR, \fB--subsystem\fR
+Invoke command (mandatory) as SSH2 subsystem.
+.TP
+\fB--log\fR
+Print the receieved data to stderr.
+.SH DESCRIPTION
+Open an SSH connection to specified server, and either run the command
+given there or open a remote interactive shell.
+.SH AUTHOR
+Written by Moshe Zadka, based on conch's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2002-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+ssh(1)
diff --git a/doc/core/benchmarks/banana.py b/doc/core/benchmarks/banana.py
new file mode 100644
index 0000000..1c1f031
--- /dev/null
+++ b/doc/core/benchmarks/banana.py
@@ -0,0 +1,10 @@
+#!/usr/bin/python
+
+from timer import timeit
+from twisted.spread.banana import b1282int
+
+ITERATIONS = 100000
+
+for length in (1, 5, 10, 50, 100):
+ elapsed = timeit(b1282int, ITERATIONS, "\xff" * length)
+ print "b1282int %3d byte string: %10d cps" % (length, ITERATIONS / elapsed)
diff --git a/doc/core/benchmarks/deferreds.py b/doc/core/benchmarks/deferreds.py
new file mode 100644
index 0000000..ddae19e
--- /dev/null
+++ b/doc/core/benchmarks/deferreds.py
@@ -0,0 +1,145 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+See how fast deferreds are.
+
+This is mainly useful to compare cdefer.Deferred to defer.Deferred
+"""
+
+
+from twisted.internet import defer
+from timer import timeit
+
+benchmarkFuncs = []
+
+def benchmarkFunc(iter, args=()):
+ """
+ A decorator for benchmark functions that measure a single iteration
+ count. Registers the function with the given iteration count to the global
+ benchmarkFuncs list
+ """
+ def decorator(func):
+ benchmarkFuncs.append((func, args, iter))
+ return func
+ return decorator
+
+def benchmarkNFunc(iter, ns):
+ """
+ A decorator for benchmark functions that measure multiple iteration
+ counts. Registers the function with the given iteration count to the global
+ benchmarkFuncs list.
+ """
+ def decorator(func):
+ for n in ns:
+ benchmarkFuncs.append((func, (n,), iter))
+ return func
+ return decorator
+
+def instantiate():
+ """
+ Only create a deferred
+ """
+ d = defer.Deferred()
+instantiate = benchmarkFunc(100000)(instantiate)
+
+def instantiateShootCallback():
+ """
+ Create a deferred and give it a normal result
+ """
+ d = defer.Deferred()
+ d.callback(1)
+instantiateShootCallback = benchmarkFunc(100000)(instantiateShootCallback)
+
+def instantiateShootErrback():
+ """
+ Create a deferred and give it an exception result. To avoid Unhandled
+ Errors, also register an errback that eats the error
+ """
+ d = defer.Deferred()
+ try:
+ 1/0
+ except:
+ d.errback()
+ d.addErrback(lambda x: None)
+instantiateShootErrback = benchmarkFunc(200)(instantiateShootErrback)
+
+ns = [10, 1000, 10000]
+
+def instantiateAddCallbacksNoResult(n):
+ """
+ Creates a deferred and adds a trivial callback/errback/both to it the given
+ number of times.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f, f)
+instantiateAddCallbacksNoResult = benchmarkNFunc(20, ns)(instantiateAddCallbacksNoResult)
+
+def instantiateAddCallbacksBeforeResult(n):
+ """
+ Create a deferred and adds a trivial callback/errback/both to it the given
+ number of times, and then shoots a result through all of the callbacks.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f)
+ d.callback(1)
+instantiateAddCallbacksBeforeResult = benchmarkNFunc(20, ns)(instantiateAddCallbacksBeforeResult)
+
+def instantiateAddCallbacksAfterResult(n):
+ """
+ Create a deferred, shoots it and then adds a trivial callback/errback/both
+ to it the given number of times. The result is processed through the
+ callbacks as they are added.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ d.callback(1)
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f)
+instantiateAddCallbacksAfterResult = benchmarkNFunc(20, ns)(instantiateAddCallbacksAfterResult)
+
+def pauseUnpause(n):
+ """
+ Adds the given number of callbacks/errbacks/both to a deferred while it is
+ paused, and unpauses it, trigerring the processing of the value through the
+ callbacks.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ d.callback(1)
+ d.pause()
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f)
+ d.unpause()
+pauseUnpause = benchmarkNFunc(20, ns)(pauseUnpause)
+
+def benchmark():
+ """
+ Run all of the benchmarks registered in the benchmarkFuncs list
+ """
+ print defer.Deferred.__module__
+ for func, args, iter in benchmarkFuncs:
+ print func.__name__, args, timeit(func, iter, *args)
+
+if __name__ == '__main__':
+ benchmark()
diff --git a/doc/core/benchmarks/failure.py b/doc/core/benchmarks/failure.py
new file mode 100644
index 0000000..d98cb49
--- /dev/null
+++ b/doc/core/benchmarks/failure.py
@@ -0,0 +1,66 @@
+
+"""See how slow failure creation is"""
+
+import random
+from twisted.python import failure
+
+random.seed(10050)
+O = [0, 20, 40, 60, 80, 10, 30, 50, 70, 90]
+DEPTH = 30
+
+def pickVal():
+ return random.choice([None, 1, 'Hello', [], {1: 1}, (1, 2, 3)])
+
+def makeLocals(n):
+ return ';'.join(['x%d = %s' % (i, pickVal()) for i in range(n)])
+
+for nLocals in O:
+ for i in range(DEPTH):
+ s = """
+def deepFailure%d_%d():
+ %s
+ deepFailure%d_%d()
+""" % (nLocals, i, makeLocals(nLocals), nLocals, i + 1)
+ exec s
+
+ exec """
+def deepFailure%d_%d():
+ 1 / 0
+""" % (nLocals, DEPTH)
+
+R = range(5000)
+def fail(n):
+ for i in R:
+ try:
+ eval('deepFailure%d_0' % n)()
+ except:
+ failure.Failure()
+
+def fail_str(n):
+ for i in R:
+ try:
+ eval('deepFailure%d_0' % n)()
+ except:
+ str(failure.Failure())
+
+class PythonException(Exception): pass
+
+def fail_easy(n):
+ for i in R:
+ try:
+ failure.Failure(PythonException())
+ except:
+ pass
+
+from timer import timeit
+# for i in O:
+# timeit(fail, 1, i)
+
+# for i in O:
+# print 'easy failing', i, timeit(fail_easy, 1, i)
+
+for i in O:
+ print 'failing', i, timeit(fail, 1, i)
+
+# for i in O:
+# print 'string failing', i, timeit(fail_str, 1, i)
diff --git a/doc/core/benchmarks/linereceiver.py b/doc/core/benchmarks/linereceiver.py
new file mode 100644
index 0000000..7f55291
--- /dev/null
+++ b/doc/core/benchmarks/linereceiver.py
@@ -0,0 +1,47 @@
+import math, time
+
+from twisted.protocols import basic
+
+class CollectingLineReceiver(basic.LineReceiver):
+ def __init__(self):
+ self.lines = []
+ self.lineReceived = self.lines.append
+
+def deliver(proto, chunks):
+ map(proto.dataReceived, chunks)
+
+def benchmark(chunkSize, lineLength, numLines):
+ bytes = ('x' * lineLength + '\r\n') * numLines
+ chunkCount = len(bytes) / chunkSize + 1
+ chunks = []
+ for n in xrange(chunkCount):
+ chunks.append(bytes[n*chunkSize:(n+1)*chunkSize])
+ assert ''.join(chunks) == bytes, (chunks, bytes)
+ p = CollectingLineReceiver()
+
+ before = time.clock()
+ deliver(p, chunks)
+ after = time.clock()
+
+ assert bytes.splitlines() == p.lines, (bytes.splitlines(), p.lines)
+
+ print 'chunkSize:', chunkSize,
+ print 'lineLength:', lineLength,
+ print 'numLines:', numLines,
+ print 'CPU Time: ', after - before
+
+
+
+def main():
+ for numLines in 100, 1000:
+ for lineLength in (10, 100, 1000):
+ for chunkSize in (1, 500, 5000):
+ benchmark(chunkSize, lineLength, numLines)
+
+ for numLines in 10000, 50000:
+ for lineLength in (1000, 2000):
+ for chunkSize in (51, 500, 5000):
+ benchmark(chunkSize, lineLength, numLines)
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/benchmarks/netstringreceiver.py b/doc/core/benchmarks/netstringreceiver.py
new file mode 100644
index 0000000..e48f66e
--- /dev/null
+++ b/doc/core/benchmarks/netstringreceiver.py
@@ -0,0 +1,242 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.test import proto_helpers, test_protocols
+import math
+import time
+import sys
+import os
+import gc
+
+NETSTRING_PREFIX_TEMPLATE ="%d:"
+NETSTRING_POSTFIX = ","
+USAGE = """\
+Usage: %s <number> <filename>
+
+This script creates up to 2 ** <number> chunks with up to 2 **
+<number> characters and sends them to the NetstringReceiver. The
+sorted performance data for all combination is written to <filename>
+afterwards.
+
+You might want to start with a small number, maybe 10 or 12, and slowly
+increase it. Stop when the performance starts to deteriorate ;-).
+"""
+
+class PerformanceTester(object):
+ """
+ A class for testing the performance of some
+ """
+
+ headers = []
+ lineFormat = ""
+ performanceData = {}
+
+ def __init__(self, filename):
+ """
+ Initializes C{self.filename}.
+
+ If a file with this name already exists, asks if it should be
+ overwritten. Terminates with exit status 1, if the user does
+ not accept.
+ """
+ if os.path.isfile(filename):
+ response = raw_input(("A file named %s exists. "
+ "Overwrite it (y/n)? ") % filename)
+ if response.lower() != "y":
+ print "Performance test cancelled."
+ sys.exit(1)
+ self.filename = filename
+
+
+ def testPerformance(self, number):
+ """
+ Drives the execution of C{performTest} with arguments between
+ 0 and C{number - 1}.
+
+ @param number: Defines the number of test runs to be performed.
+ @type number: C{int}
+ """
+ for iteration in xrange(number):
+ self.performTest(iteration)
+
+
+ def performTest(self, iteration):
+ """
+ Performs one test iteration. Overwrite this.
+
+ @param iteration: The iteration number. Can be used to configure
+ the test.
+ @type iteration: C{int}
+ @raise NotImplementedError: because this method has to be implemented
+ by the subclass.
+ """
+ raise NotImplementedError
+
+
+ def createReport(self):
+ """
+ Creates a file and writes a table with performance data.
+
+ The performance data are ordered by the total size of the netstrings.
+ In addition they show the chunk size, the number of chunks and the
+ time (in seconds) that elapsed while the C{NetstringReceiver}
+ received the netstring.
+
+ @param filename: The name of the report file that will be written.
+ @type filename: C{str}
+ """
+ self.outputFile = open(self.filename, "w")
+ self.writeHeader()
+ self.writePerformanceData()
+ self.writeLineSeparator()
+ print "The report was written to %s." % self.filename
+
+
+ def writeHeader(self):
+ """
+ Writes the table header for the report.
+ """
+ self.writeLineSeparator()
+ self.outputFile.write("| %s |\n" % (" | ".join(self.headers),))
+ self.writeLineSeparator()
+
+
+ def writeLineSeparator(self):
+ """
+ Writes a 'line separator' made from '+' and '-' characters.
+ """
+ dashes = ("-" * (len(header) + 2) for header in self.headers)
+ self.outputFile.write("+%s+\n" % "+".join(dashes))
+
+
+ def writePerformanceData(self):
+ """
+ Writes one line for each item in C{self.performanceData}.
+ """
+ for combination, elapsed in sorted(self.performanceData.iteritems()):
+ totalSize, chunkSize, numberOfChunks = combination
+ self.outputFile.write(self.lineFormat %
+ (totalSize, chunkSize, numberOfChunks,
+ elapsed))
+
+
+
+class NetstringPerformanceTester(PerformanceTester):
+ """
+ A class for determining the C{NetstringReceiver.dataReceived} performance.
+
+ Instantiates a C{NetstringReceiver} and calls its
+ C{dataReceived()} method with different chunks sizes and numbers
+ of chunks. Presents a table showing the relation between input
+ data and time to process them.
+ """
+
+ headers = ["Chunk size", "Number of chunks", "Total size",
+ "Time to receive" ]
+ lineFormat = ("| %%%dd | %%%dd | %%%dd | %%%d.4f |\n" %
+ tuple([len(header) for header in headers]))
+
+ def __init__(self, filename):
+ """
+ Sets up the output file and the netstring receiver that will be
+ used for receiving data.
+
+ @param filename: The name of the file for storing the report.
+ @type filename: C{str}
+ """
+ PerformanceTester.__init__(self, filename)
+ transport = proto_helpers.StringTransport()
+ self.netstringReceiver = test_protocols.TestNetstring()
+ self.netstringReceiver.makeConnection(transport)
+
+
+ def performTest(self, number):
+ """
+ Tests the performance of C{NetstringReceiver.dataReceived}.
+
+ Feeds netstrings of various sizes in different chunk sizes
+ to a C{NetstringReceiver} and stores the elapsed time in
+ C{self.performanceData}.
+
+ @param number: The maximal chunks size / number of
+ chunks to be checked.
+ @type number: C{int}
+ """
+ chunkSize = 2 ** number
+ numberOfChunks = chunkSize
+ while numberOfChunks:
+ self.testCombination(chunkSize, numberOfChunks)
+ numberOfChunks = numberOfChunks // 2
+
+
+ def testCombination(self, chunkSize, numberOfChunks):
+ """
+ Tests one combination of chunk size and number of chunks.
+
+ @param chunkSize: The size of one chunk to be sent to the
+ C{NetstringReceiver}.
+ @type chunkSize: C{int}
+ @param numberOfChunks: The number of C{chunkSize}-sized chunks to
+ be sent to the C{NetstringReceiver}.
+ @type numberOfChunks: C{int}
+ """
+ chunk, dataSize = self.configureCombination(chunkSize, numberOfChunks)
+ elapsed = self.receiveData(chunk, numberOfChunks, dataSize)
+ key = (chunkSize, numberOfChunks, dataSize)
+ self.performanceData[key] = elapsed
+
+
+ def configureCombination(self, chunkSize, numberOfChunks):
+ """
+ Updates C{MAX_LENGTH} for {self.netstringReceiver} (to avoid
+ C{NetstringParseErrors} that might be raised if the size
+ exceeds the default C{MAX_LENGTH}).
+
+ Calculates and returns one 'chunk' of data and the total size
+ of the netstring.
+
+ @param chunkSize: The size of chunks that will be received.
+ @type chunkSize: C{int}
+ @param numberOfChunks: The number of C{chunkSize}-sized chunks
+ that will be received.
+ @type numberOfChunks: C{int}
+
+ @return: A tuple consisting of string of C{chunkSize} 'a'
+ characters and the size of the netstring data portion.
+ """
+ chunk = "a" * chunkSize
+ dataSize = chunkSize * numberOfChunks
+ self.netstringReceiver.MAX_LENGTH = dataSize
+ numberOfDigits = math.ceil(math.log10(dataSize)) + 1
+ return chunk, dataSize
+
+
+ def receiveData(self, chunk, numberOfChunks, dataSize):
+ dr = self.netstringReceiver.dataReceived
+ now = time.time()
+ dr(NETSTRING_PREFIX_TEMPLATE % (dataSize,))
+ for idx in xrange(numberOfChunks):
+ dr(chunk)
+ dr(NETSTRING_POSTFIX)
+ elapsed = time.time() - now
+ assert self.netstringReceiver.received, "Didn't receive string!"
+ return elapsed
+
+
+def disableGarbageCollector():
+ gc.disable()
+ print 'Disabled Garbage Collector.'
+
+
+def main(number, filename):
+ disableGarbageCollector()
+ npt = NetstringPerformanceTester(filename)
+ npt.testPerformance(int(number))
+ npt.createReport()
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 3:
+ print USAGE % sys.argv[0]
+ sys.exit(1)
+ main(*sys.argv[1:3])
diff --git a/doc/core/benchmarks/task.py b/doc/core/benchmarks/task.py
new file mode 100644
index 0000000..e3d437b
--- /dev/null
+++ b/doc/core/benchmarks/task.py
@@ -0,0 +1,26 @@
+
+"""
+Benchmarks for L{twisted.internet.task}.
+"""
+
+from timer import timeit
+
+from twisted.internet import task
+
+def test_performance():
+ """
+ L{LoopingCall} should not take long to skip a lot of iterations.
+ """
+ clock = task.Clock()
+ call = task.LoopingCall(lambda: None)
+ call.clock = clock
+
+ call.start(0.1)
+ clock.advance(1000000)
+
+
+def main():
+ print "LoopingCall large advance takes", timeit(test_performance, iter=1)
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/benchmarks/timer.py b/doc/core/benchmarks/timer.py
new file mode 100644
index 0000000..78181c6
--- /dev/null
+++ b/doc/core/benchmarks/timer.py
@@ -0,0 +1,24 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Helper stuff for benchmarks.
+"""
+
+import gc
+gc.disable()
+print 'Disabled GC'
+
+def timeit(func, iter = 1000, *args, **kwargs):
+ """
+ timeit(func, iter = 1000 *args, **kwargs) -> elapsed time
+
+ calls func iter times with args and kwargs, returns time elapsed
+ """
+
+ from time import time as currentTime
+ r = range(iter)
+ t = currentTime()
+ for i in r:
+ func(*args, **kwargs)
+ return currentTime() - t
diff --git a/doc/core/benchmarks/tpclient.py b/doc/core/benchmarks/tpclient.py
new file mode 100644
index 0000000..9e5e082
--- /dev/null
+++ b/doc/core/benchmarks/tpclient.py
@@ -0,0 +1,60 @@
+"""Throughput test."""
+
+import time, sys
+from twisted.internet import reactor, protocol
+from twisted.python import log
+
+TIMES = 10000
+S = "0123456789" * 1240
+
+toReceive = len(S) * TIMES
+
+class Sender(protocol.Protocol):
+
+ def connectionMade(self):
+ start()
+ self.numSent = 0
+ self.received = 0
+ self.transport.registerProducer(self, 0)
+
+ def stopProducing(self):
+ pass
+
+ def pauseProducing(self):
+ pass
+
+ def resumeProducing(self):
+ self.numSent += 1
+ self.transport.write(S)
+ if self.numSent == TIMES:
+ self.transport.unregisterProducer()
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ shutdown(self.numSent == TIMES)
+
+
+started = None
+
+def start():
+ global started
+ started = time.time()
+
+def shutdown(success):
+ if not success:
+ raise SystemExit, "failure or something"
+ passed = time.time() - started
+ print "Throughput (send): %s kbytes/sec" % ((toReceive / passed) / 1024)
+ reactor.stop()
+
+
+def main():
+ f = protocol.ClientFactory()
+ f.protocol = Sender
+ reactor.connectTCP(sys.argv[1], int(sys.argv[2]), f)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ #log.startLogging(sys.stdout)
+ main()
diff --git a/doc/core/benchmarks/tpclient_nt.py b/doc/core/benchmarks/tpclient_nt.py
new file mode 100644
index 0000000..a8170d7
--- /dev/null
+++ b/doc/core/benchmarks/tpclient_nt.py
@@ -0,0 +1,22 @@
+"""Non-twisted throughput client."""
+
+import socket, time, sys
+
+TIMES = 50000
+S = "0123456789" * 1024
+sent = len(S) * TIMES
+
+def main():
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((sys.argv[1], int(sys.argv[2])))
+ start = time.time()
+ i = 0
+ while i < TIMES:
+ i += 1
+ s.sendall(S)
+ passed = time.time() - start
+ print "Throughput: %s kbytes/sec" % ((sent / passed) / 1024)
+ s.close()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/benchmarks/tpserver.py b/doc/core/benchmarks/tpserver.py
new file mode 100644
index 0000000..49024e1
--- /dev/null
+++ b/doc/core/benchmarks/tpserver.py
@@ -0,0 +1,19 @@
+"""Throughput server."""
+
+import sys
+
+from twisted.protocols.wire import Discard
+from twisted.internet import protocol, reactor
+from twisted.python import log
+
+
+def main():
+ f = protocol.ServerFactory()
+ f.protocol = Discard
+ reactor.listenTCP(8000, f)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
+
diff --git a/doc/core/benchmarks/tpserver_nt.py b/doc/core/benchmarks/tpserver_nt.py
new file mode 100644
index 0000000..e4bfdda
--- /dev/null
+++ b/doc/core/benchmarks/tpserver_nt.py
@@ -0,0 +1,22 @@
+"""Non-twisted throughput server."""
+
+import socket, signal, sys
+
+def signalhandler(*args):
+ print "alarm!"
+ sys.stdout.flush()
+
+signal.signal(signal.SIGALRM, signalhandler)
+
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.bind(('', 8001))
+s.listen(1)
+while 1:
+ c, (h, p) = s.accept()
+ c.settimeout(30)
+ signal.alarm(5)
+ while 1:
+ d = c.recv(16384)
+ if not d:
+ break
+ c.close()
diff --git a/doc/core/development/index.html b/doc/core/development/index.html
new file mode 100644
index 0000000..183d76b
--- /dev/null
+++ b/doc/core/development/index.html
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Development of Twisted</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Development of Twisted</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>This documentation is for people who work on the Twisted codebase itself,
+rather than for people who want to use Twisted in their own projects.</p>
+<ul>
+<li><a href="naming.html" shape="rect">Naming</a></li>
+<li><a href="philosophy.html" shape="rect">Philosophy</a></li>
+<li><a href="security.html" shape="rect">Security</a></li>
+<li><a href="policy/" shape="rect">Twisted development policy</a></li>
+</ul>
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/development/listings/new_module_template.py b/doc/core/development/listings/new_module_template.py
new file mode 100644
index 0000000..85cf04b
--- /dev/null
+++ b/doc/core/development/listings/new_module_template.py
@@ -0,0 +1,12 @@
+# -*- test-case-name: <test module> -*-
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Docstring goes here.
+"""
+
+
+__all__ = []
diff --git a/doc/core/development/naming.html b/doc/core/development/naming.html
new file mode 100644
index 0000000..1f139dd
--- /dev/null
+++ b/doc/core/development/naming.html
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Naming Conventions</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Naming Conventions</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<p>While this may sound like a small detail, clear method naming is important to provide an API that developers familiar with event-based programming can pick up quickly.</p>
+
+<p>Since the idea of a method call maps very neatly onto that of a received event, all event handlers are simply methods named after past-tense verbs. All class names are descriptive nouns, designed to mirror the is-a relationship of the abstractions they implement. All requests for notification or transmission are present-tense imperative verbs.</p>
+
+<p>Here are some examples of this naming scheme:</p>
+
+<ul>
+<li>An event notification of data received from peer:
+<code class="python">dataReceived(data)</code></li>
+<li>A request to send data: <code class="python">write(data)</code></li>
+<li>A class that implements a protocol: <code class="python">Protocol</code></li>
+</ul>
+
+<p>The naming is platform neutral. This means that the names are equally appropriate in a wide variety of environments, as long as they can publish the required events.</p>
+
+<p>It is self-consistent. Things that deal with TCP use the acronym TCP, and it is always capitalized. Dropping, losing, terminating, and closing the connection are all referred to as <q>losing</q> the connection. This symmetrical naming allows developers to easily locate other API calls if they have learned a few related to what they want to do.</p>
+
+<p>It is semantically clear. The semantics of dataReceived are simple: there are some bytes available for processing. This remains true even if the lower-level machinery to get the data is highly complex.</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/development/philosophy.html b/doc/core/development/philosophy.html
new file mode 100644
index 0000000..0dd2ebc
--- /dev/null
+++ b/doc/core/development/philosophy.html
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Philosophy</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Philosophy</h1>
+ <div class="toc"><ol><li><a href="#auto0">Abstraction Levels</a></li><li><a href="#auto1">Learning Curves</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Abstraction Levels<a name="auto0"/></h2>
+
+<p>When implementing interfaces to the operating system or
+the network, provide two interfaces:</p>
+
+<ul>
+<li>One that doesn't hide platform specific or library specific
+functionality.
+For example, you can use file descriptors on Unix, and Win32 events on
+Windows.
+</li>
+<li>One that provides a high level interface hiding platform specific
+details.
+E.g. process running uses same API on Unix and Windows, although
+the implementation is very different.
+</li>
+</ul>
+
+<p>Restated in a more general way:</p>
+
+<ul>
+<li>Provide all low level functionality for your specific domain,
+without limiting the policies and decisions the user can make.</li>
+<li>Provide a high level abstraction on top of the low level
+implementation (or implementations) which implements the
+common use cases and functionality that is used in most cases.</li>
+</ul>
+
+<h2>Learning Curves<a name="auto1"/></h2>
+
+<p>Require the minimal amount of work and learning on part of the
+user to get started. If this means they have less functionality,
+that's OK, when they need it they can learn a bit more. This
+will also lead to a cleaner, easier to test design.</p>
+
+<p>For example - using twistd is a great way to deploy applications.
+But to get started you don't need to know about it. Later on you can
+start using twistd, but its usage is optional.</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/development/policy/coding-standard.html b/doc/core/development/policy/coding-standard.html
new file mode 100644
index 0000000..c83ae77
--- /dev/null
+++ b/doc/core/development/policy/coding-standard.html
@@ -0,0 +1,818 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Coding Standard</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Coding Standard</h1>
+ <div class="toc"><ol><li><a href="#auto0">Naming</a></li><li><a href="#auto1">Testing</a></li><ul><li><a href="#auto2">Overview</a></li><li><a href="#auto3">Test Suite</a></li></ul><li><a href="#auto4">Copyright Header</a></li><li><a href="#auto5">Whitespace</a></li><li><a href="#auto6">Modules</a></li><li><a href="#auto7">Packages</a></li><li><a href="#auto8">String Formatting Operations</a></li><li><a href="#auto9">Docstrings</a></li><li><a href="#auto10">Comments</a></li><li><a href="#auto11">Versioning</a></li><li><a href="#auto12">Scripts</a></li><li><a href="#auto13">Examples</a></li><li><a href="#auto14">Standard Library Extension Modules</a></li><li><a href="#auto15">Classes</a></li><ul><li><a href="#auto16">New-style Classes</a></li></ul><li><a href="#auto17">Methods</a></li><li><a href="#auto18">Callback Arguments</a></li><li><a href="#auto19">Special Methods</a></li><li><a href="#auto20">Functions</a></li><li><a href="#auto21">Attributes</a></li><li><a href="#auto22">Database</a></li><li><a href="#auto23">C Code</a></li><li><a href="#auto24">Commit Messages</a></li><li><a href="#auto25">Source Control</a></li><li><a href="#auto26">Fallback</a></li><li><a href="#auto27">Recommendations</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Naming<a name="auto0"/></h2>
+
+ <p>Try to choose names which are both easy to remember and
+ meaningful. Some silliness is OK at the module naming level
+ (see <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.html" title="twisted.spread">twisted.spread</a></code>...) but when
+ choosing class names, be as precise as possible.</p>
+
+ <p>Try to avoid overloaded terms. This rule is often broken,
+ since it is incredibly difficult, as most normal words have
+ already been taken by some other software. More importantly,
+ try to avoid meaningless words. In particular, words like
+ <q>handler</q>, <q>processor</q>, <q>engine</q>, <q>manager</q>
+ and <q>component</q> don't really indicate what something does,
+ only that it does <em>something</em>.</p>
+
+ <p>Use American spelling in both names and docstrings. For compound
+ technical terms such as 'filesystem', use a non-hyphenated spelling in
+ both docstrings and code in order to avoid unnecessary
+ capitalization.</p>
+
+ <h2>Testing<a name="auto1"/></h2>
+
+ <h3>Overview<a name="auto2"/></h3>
+
+ <p>Twisted development should always be
+ <a href="http://en.wikipedia.org/wiki/Test-driven_development" shape="rect">
+ test-driven</a>. The complete test suite in the head of the SVN trunk is required to
+ be passing on <a href="http://buildbot.twistedmatrix.com/supported" shape="rect">
+ supported platforms</a> at all times. Regressions in the test suite
+ are addressed by reverting whatever revisions introduced them. For
+ complete documentation about testing Twisted itself, refer to the
+ <a href="test-standard.html" shape="rect">Test Standard</a>. What follows is
+ intended to be a synopsis of the most important points.</p>
+
+ <h3>Test Suite<a name="auto3"/></h3>
+
+ <p>The Twisted test suite is spread across many subpackages of the
+ <code>twisted</code> package. Many older tests are in
+ <code>twisted.test</code>. Others can be found at places such as
+ <code>twisted.web.test</code> (for <code>twisted.web</code> tests)
+ or <code>twisted.internet.test</code> (for <code>twisted.internet</code>
+ tests). The latter arrangement, <code>twisted.somepackage.test</code>,
+ is preferred for new tests except when a test module already exists in
+ <code>twisted.test</code>.
+ </p>
+
+ <p>
+ Parts of the Twisted test suite may serve as good examples of how to
+ write tests for Twisted or for Twisted-based libraries (newer parts of
+ the test suite are generally better examples than older parts - check
+ when the code you are looking at was written before you use it as an
+ example of what you should write). The names of test modules should
+ begin with <code>test_</code> so that they are automatically discoverable by
+ test runners such as Trial. Twisted's unit tests are written using
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.trial.html" title="twisted.trial">twisted.trial</a></code>, an xUnit library which has been
+ extensively customized for use in testing Twisted and Twisted-based
+ libraries.</p>
+
+ <p>Implementation (ie, non-test) source files should begin with a
+ <code>test-case-name</code> tag which gives the name of any test
+ modules or packages which exercise them. This lets tools discover a
+ subset of the entire test suite which they can run first to find tests
+ which might be broken by a particular change.</p>
+
+ <p>It is strongly suggested that developers learn to use Emacs, and use
+ the <code>twisted-dev.el</code> file included in
+ <a href="http://launchpad.net/twisted-emacs" shape="rect">twisted-emacs</a>
+ to bind the F9 key to <q>run unit tests</q> and bang on it
+ frequently. Support for other editors is unavailable at this time but
+ we would love to provide it.</p>
+
+ <p>To run the whole Twisted test without using emacs, use trial:</p>
+
+ <pre class="shell" xml:space="preserve">
+$ bin/trial twisted
+ </pre>
+
+ <p>To run an individual test module, such as
+ <code>twisted/mail/test/test_pop3.py</code>, specify the module
+ name:</p>
+
+ <pre class="shell" xml:space="preserve">
+$ bin/trial twisted.mail.test.test_pop3
+ </pre>
+
+ <p>To run the tests associated with a particular implementation file,
+ such as <code>twisted/mail/pop3.py</code>, use the
+ <code>testmodule</code> option:</p>
+
+ <pre class="shell" xml:space="preserve">
+$ bin/trial twisted/mail/pop3.py
+ </pre>
+
+ <p>All unit test methods should have docstrings specifying at a high
+ level the intent of the test. That is, a description that users of the
+ method would understand.</p>
+
+ <p>If you modify, or write a new, HOWTO, please read the <a href="http://twistedmatrix.com/trac/wiki/TwistedLore" shape="rect">Lore</a>
+ documentation to learn how to format the docs.</p>
+
+ <h2>Copyright Header<a name="auto4"/></h2>
+
+ <p>Whenever a new file is added to the repository, add the following
+ license header at the top of the file:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+</pre>
+
+ <p>When you update existing files, if there is no copyright header, add
+ one.</p>
+
+ <h2>Whitespace<a name="auto5"/></h2>
+
+ <p>Indentation is 4 spaces per indent. Tabs are not allowed. It
+ is preferred that every block appear on a new line, so that
+ control structure indentation is always visible.</p>
+
+ <p>Lines are flowed at 79 columns. They must not have trailing
+ whitespace. Long lines must be wrapped using implied line continuation
+ inside parentheses; backslashes aren't allowed. To handle long import
+ lines, please repeat the import like this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">very</span>.<span class="py-src-variable">long</span>.<span class="py-src-variable">package</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">foo</span>, <span class="py-src-variable">bar</span>, <span class="py-src-variable">baz</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">very</span>.<span class="py-src-variable">long</span>.<span class="py-src-variable">package</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">qux</span>, <span class="py-src-variable">quux</span>, <span class="py-src-variable">quuux</span>
+</pre>
+
+ <p>Top-level classes and functions must be separated with 3 blank lines,
+ and class-level functions with 2 blank lines. The control-L (i.e. ^L) form
+ feed character must not be used.</p>
+
+ <h2>Modules<a name="auto6"/></h2>
+
+ <p>Modules must be named in all lower-case, preferably short,
+ single words. If a module name contains multiple words, they
+ may be separated by underscores or not separated at all.</p>
+
+ <p>Modules must have a copyright message, a docstring and a
+ reference to a test module that contains the bulk of its tests.
+ Use this template:</p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-comment"># -*- test-case-name: &lt;test module&gt; -*-</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+
+<span class="py-src-string">&quot;&quot;&quot;
+Docstring goes here.
+&quot;&quot;&quot;</span>
+
+
+<span class="py-src-variable">__all__</span> = []
+</pre><div class="caption">Source listing - <a href="../listings/new_module_template.py"><span class="filename">../listings/new_module_template.py</span></a></div></div>
+
+ <p>In most cases, modules should contain more than one class,
+ function, or method; if a module contains only one object,
+ consider refactoring to include more related functionality in
+ that module.</p>
+
+ <p>Depending on the situation, it is acceptable to have imports that
+ look like this:
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Deferred</span>
+</pre>
+ or like this:
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+</pre>
+ That is, modules should import <em>modules</em> or <em>classes and
+ functions</em>, but not <em>packages</em>.</p>
+
+ <p>Wildcard import syntax may not be used by code in Twisted. These
+ imports lead to code which is difficult to read and maintain by
+ introducing complexity which strains human readers and automated tools
+ alike. If you find yourself with many imports to make from a single
+ module and wish to save typing, consider importing the module itself,
+ rather than its attributes.</p>
+
+ <p><em>Relative imports</em> (or <em>sibling imports</em>) may not be
+ used by code in Twisted. Relative imports allow certain circularities
+ to be introduced which can ultimately lead to unimportable modules or
+ duplicate instances of a single module. Relative imports also make the
+ task of refactoring more difficult.</p>
+
+ <p>In case of local names conflicts due to import, use the <code>as</code>
+ syntax, for example:
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">util</span> <span class="py-src-keyword">as</span> <span class="py-src-variable">trial_util</span>
+</pre></p>
+
+ <p>The encoding must always be ASCII, so no coding cookie is necessary.</p>
+
+ <h2>Packages<a name="auto7"/></h2>
+
+ <p>Package names should follow the same conventions as module
+ names. All modules must be encapsulated in some package. Nested
+ packages may be used to further organize related modules.</p>
+
+ <p><code>__init__.py</code> must never contain anything other than a
+ docstring and (optionally) an <code>__all__</code> attribute. Packages are
+ not modules and should be treated differently. This rule may be
+ broken to preserve backwards compatibility if a module is made
+ into a nested package as part of a refactoring.</p>
+
+ <p>If you wish to promote code from a module to a package, for
+ example, to break a large module out into several smaller
+ files, the accepted way to do this is to promote from within
+ the module. For example,</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-comment"># parent/</span>
+<span class="py-src-comment"># --- __init__.py ---</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">child</span>
+
+<span class="py-src-comment"># --- child.py ---</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">parent</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Foo</span>:
+ <span class="py-src-keyword">pass</span>
+<span class="py-src-variable">parent</span>.<span class="py-src-variable">Foo</span> = <span class="py-src-variable">Foo</span>
+</pre>
+
+ <p>Every package should be added to the list in
+ <code class="shell">setup.py</code>.</p>
+
+ <p>Packages must not depend circularly upon each other. To simplify
+ maintaining this state, packages must also not import each other
+ circularly. While this applies to all packages within Twisted, one
+ <code>twisted.python</code> deserves particular attention, as it may
+ not depend on any other Twisted package.</p>
+
+ <h2>String Formatting Operations<a name="auto8"/></h2>
+
+ <p>When using <a href="http://docs.python.org/lib/typesseq-strings.html" shape="rect">string formatting
+ operations</a> like <code>formatString % values</code> you should always
+ use a tuple if you're using non-mapping <code>values</code>. This is to
+ avoid unexpected behavior when you think you're passing in a single value,
+ but the value is unexpectedly a tuple, e.g.:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">foo</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Hi %s\n&quot;</span> % <span class="py-src-variable">x</span>
+</pre>
+
+ <p>The example shows you can pass in <code>foo(&quot;foo&quot;)</code> or
+ <code>foo(3)</code> fine, but if you pass in <code>foo((1,2))</code>,
+ it raises a <code>TypeError</code>. You should use this instead:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">foo</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Hi %s\n&quot;</span> % (<span class="py-src-variable">x</span>,)
+</pre>
+
+ <h2>Docstrings<a name="auto9"/></h2>
+
+ <p>Docstrings should always be used to describe the
+ purpose of methods, functions, classes, and modules.</p>
+
+ <p>Docstrings are <em>never</em> to be used to provide semantic
+ information about an object; this rule may be violated if the
+ code in question is to be used in a system where this is a
+ requirement (such as Zope).</p>
+
+ <p>Docstrings should be indented to the level of the code they
+ are documenting.</p>
+
+ <p>Docstrings should be triple-quoted. The opening and the closing of the
+ docstrings should be on a line by themselves. For example:
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Ninja</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ A L{Ninja} is a warrior specializing in various unorthodox arts of war.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">attack</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">someone</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Attack C{someone} with this L{Ninja}'s shuriken.
+ &quot;&quot;&quot;</span>
+</pre>
+ </p>
+
+ <p>Docstrings should be written in epytext format; more
+ documentation is available in the
+ <a href="http://epydoc.sourceforge.net/manual-epytext.html" shape="rect">Epytext Markup Language documentation</a>.</p>
+
+ <p>Additionally, to accommodate emacs users, single quotes of the type of
+ the docstring's triple-quote should be escaped. This will prevent font-lock from
+ accidentally fontifying large portions of the file as a string.</p>
+
+ <p>For example,</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">foo2bar</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Convert L{foo}s to L{bar}s.
+
+ A function that should be used when you have a C{foo} but you want a
+ C{bar}; note that this is a non-destructive operation. If this method
+ can't convert the C{foo} to a C{bar} it will raise a L{FooException}.
+
+ @param f: C{foo}
+ @type f: str
+
+ For example::
+
+ import wombat
+ def sample(something):
+ f = something.getFoo()
+ f.doFooThing()
+ b = wombat.foo2bar(f)
+ b.doBarThing()
+ return b
+
+ &quot;&quot;&quot;</span>
+ <span class="py-src-comment"># Optionally, actual code can go here.</span>
+</pre>
+
+ <h2>Comments<a name="auto10"/></h2>
+
+ <p>Comments marked with XXX or TODO must contain a reference to the
+ associated ticket.</p>
+
+ <h2>Versioning<a name="auto11"/></h2>
+
+ <p>The API documentation should be marked up with version information.
+ When a new API is added the class should be marked with the epytext
+ <code class="shell">@since:</code> field including the version number when
+ the change was introduced, eg. <code class="shell">@since: 8.1</code>.</p>
+
+ <h2>Scripts<a name="auto12"/></h2>
+
+ <p>For each <q>script</q>, that is, a program you expect a Twisted user
+ to run from the command-line, the following things must be done:</p>
+
+ <ol>
+ <li>Write a module in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.scripts.html" title="twisted.scripts">twisted.scripts</a></code>
+ which contains a callable global named <code>run</code>. This
+ will be called by the command line part with no arguments (it
+ will usually read <code>sys.argv</code>). Feel free to write more
+ functions or classes in this module, if you feel they are useful
+ to others.</li>
+
+ <li>Create a file which contains a shebang line for Python. For Twisted
+ Core, this file should be placed in the <code>bin/</code> directory; for
+ example, <code>bin/twistd</code>. For sub-projects, it should be placed
+ in <code>bin/&lt;subproject&gt;</code>; for example, the key-generation tool
+ for the Conch sub-project is in <code>bin/conch/ckeygen</code>.
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+</pre></li>
+
+ <p>To make sure that the script is portable across different UNIX like
+ operating systems we use the <code>/usr/bin/env</code> command. The env
+ command allows you to run a program in a modified environment. That way
+ you don't have to search for a program via the <code>PATH</code> environment
+ variable. This makes the script more portable but note that it is not a
+ foolproof method. Always make sure that <code>/usr/bin/env</code> exists or
+ use a softlink/symbolic link to point it to the correct path. Python's
+ distutils will rewrite the shebang line upon installation so this policy
+ only covers the source files in version control.</p>
+
+ <li>For core scripts, add this Twisted running-from-SVN header:
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>
+<span class="py-src-keyword">try</span>:
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">_preamble</span>
+<span class="py-src-keyword">except</span> <span class="py-src-variable">ImportError</span>:
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">clear_exc</span>()
+</pre>
+
+ Or for sub-project scripts, add a modified version which also adjusts <code>sys.path</code>:
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>, <span class="py-src-variable">os</span>
+<span class="py-src-variable">extra</span> = <span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">dirname</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">dirname</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>]))
+<span class="py-src-variable">sys</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">insert</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">extra</span>)
+<span class="py-src-keyword">try</span>:
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">_preamble</span>
+<span class="py-src-keyword">except</span> <span class="py-src-variable">ImportError</span>:
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">clear_exc</span>()
+<span class="py-src-variable">sys</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">extra</span>)
+</pre></li>
+
+ <li>And end with:
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">scripts</span>.<span class="py-src-variable">yourmodule</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">run</span>
+<span class="py-src-variable">run</span>()
+</pre></li>
+
+ <li>Write a manpage and add it to the <code class="shell">man</code> folder
+ of a subproject's <code class="shell">doc</code> folder. On Debian systems
+ you can find a skeleton example of a manpage in
+ <code>/usr/share/doc/man-db/examples/manpage.example</code>.</li>
+ </ol>
+
+ <p>This will insure your program will work correctly for users of SVN,
+ Windows releases and Debian packages.</p>
+
+ <h2>Examples<a name="auto13"/></h2>
+
+ <p>For example scripts you expect a Twisted user
+ to run from the command-line, add this Python shebang line at the top
+ of the file:</p>
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+</pre>
+
+ <h2>Standard Library Extension Modules<a name="auto14"/></h2>
+
+ <p>When using the extension version of a module for which there is also
+ a Python version, place the import statement inside a try/except block,
+ and import the Python version if the import fails. This allows code to
+ work on platforms where the extension version is not available. For
+ example:
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">try</span>:
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">cPickle</span> <span class="py-src-keyword">as</span> <span class="py-src-variable">pickle</span>
+<span class="py-src-keyword">except</span> <span class="py-src-variable">ImportError</span>:
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">pickle</span>
+</pre>
+
+ Use the &quot;as&quot; syntax of the import statement as well, to set
+ the name of the extension module to the name of the Python module.</p>
+
+ <p>Some modules don't exist across all supported Python versions. For
+ example, Python 2.3's <code>sets</code> module was deprecated in Python 2.6
+ in favor of the <code>set</code> and <code>frozenset</code> builtins. When
+ you need to use sets or frozensets in your code, please use
+ the <code>set</code> and <code>frozenset</code> provided
+ by <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.compat.html" title="twisted.python.compat">twisted.python.compat</a></code>. There are some
+ differences between <code>sets.Set</code> and <code>set</code>, that are
+ explained in the <a href="http://www.python.org/dev/peps/pep-0218/" shape="rect">set
+ PEP</a>. Please be sure to not rely on the behavior of one or the other
+ implementation.</p>
+
+ <h2>Classes<a name="auto15"/></h2>
+
+ <p>Classes are to be named in mixed case, with the first letter
+ capitalized; each word separated by having its first letter
+ capitalized. Acronyms should be capitalized in their entirety.
+ Class names should not be prefixed with the name of the module they are
+ in. Examples of classes meeting this criteria:</p>
+
+ <ul>
+ <li>twisted.spread.pb.ViewPoint</li>
+ <li>twisted.parser.patterns.Pattern</li>
+ </ul>
+
+ <p>Examples of classes <strong>not</strong> meeting this criteria:</p>
+
+ <ul>
+ <li>event.EventHandler</li>
+ <li>main.MainGadget</li>
+ </ul>
+
+ <p>An effort should be made to prevent class names from clashing
+ with each other between modules, to reduce the need for
+ qualification when importing. For example, a Service subclass
+ for Forums might be named twisted.forum.service.ForumService,
+ and a Service subclass for Words might be
+ twisted.words.service.WordsService. Since neither of these
+ modules are volatile <em>(see above)</em> the classes may be
+ imported directly into the user's namespace and not cause
+ confusion.</p>
+
+ <h3>New-style Classes<a name="auto16"/></h3>
+
+ <p>Classes and instances in Python come in two flavors: old-style or
+ classic, and new-style. Up to Python 2.1, old-style classes were the
+ only flavour available to the user, new-style classes were introduced
+ in Python 2.2 to unify classes and types. All classes added to Twisted
+ should be written as new-style classes. If <code class="python">x</code>
+ is an instance of a new-style class, then <code class="python">type(x)</code>
+ is the same as <code class="python">x.__class__</code>.</p>
+
+ <h2>Methods<a name="auto17"/></h2>
+
+ <p>Methods should be in mixed case, with the first letter lower
+ case, each word separated by having its first letter
+ capitalized. For example, <code>someMethodName</code>,
+ <code>method</code>.</p>
+
+ <p>Sometimes, a class will dispatch to a specialized sort of
+ method using its name; for example, twisted.reflect.Accessor.
+ In those cases, the type of method should be a prefix in all
+ lower-case with a trailing underscore, so method names will
+ have an underscore in them. For example, <code>get_someAttribute</code>.
+ Underscores in method names in twisted code are therefore
+ expected to have some semantic associated with them.</p>
+
+ <p>Some methods, in particular <code>addCallback</code> and its
+ cousins return self to allow for chaining calls. In this case,
+ wrap the chain in parenthesis, and start each chained call on
+ a separate line, for example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">return</span> (<span class="py-src-variable">foo</span>()
+ .<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">bar</span>)
+ .<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">thud</span>)
+ .<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">wozers</span>))
+</pre>
+
+ <h2>Callback Arguments<a name="auto18"/></h2>
+
+ <p>There are several methods whose purpose is to help the user set up
+ callback functions, for example <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.addCallback.html" title="twisted.internet.defer.Deferred.addCallback">Deferred.addCallback</a></code> or the
+ reactor's <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.base.ReactorBase.callLater.html" title="twisted.internet.base.ReactorBase.callLater">callLater</a></code> method. To make
+ access to the callback as transparent as possible, most of these methods
+ use <code class="python">**kwargs</code> to capture arbitrary arguments
+ that are destined for the user's callback. This allows the call to the
+ setup function to look very much like the eventual call to the target
+ callback function.</p>
+
+ <p>In these methods, take care to not have other argument names that will
+ <q>steal</q> the user's callback's arguments. When sensible, prefix these
+ <q>internal</q> argument names with an underscore. For example, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.callRemote.html" title="twisted.spread.pb.RemoteReference.callRemote">RemoteReference.callRemote</a></code> is
+ meant to be called like this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-variable">myref</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;addUser&quot;</span>, <span class="py-src-string">&quot;bob&quot;</span>, <span class="py-src-string">&quot;555-1212&quot;</span>)
+
+<span class="py-src-comment"># on the remote end, the following method is invoked:</span>
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">addUser</span>(<span class="py-src-parameter">name</span>, <span class="py-src-parameter">phone</span>):
+ ...
+</pre>
+
+ <p>where <q>addUser</q> is the remote method name. The user might also
+ choose to call it with named parameters like this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">myref</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;addUser&quot;</span>, <span class="py-src-variable">name</span>=<span class="py-src-string">&quot;bob&quot;</span>, <span class="py-src-variable">phone</span>=<span class="py-src-string">&quot;555-1212&quot;</span>)
+</pre>
+
+ <p>In this case, <code>callRemote</code> (and any code that uses the
+ <code class="python">**kwargs</code> syntax) must be careful to not use
+ <q>name</q>, <q>phone</q>, or any other name that might overlap with
+ a user-provided named parameter. Therefore, <code>callRemote</code> is
+ implemented with the following signature:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SomeClass</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callRemote</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">_name</span>, *<span class="py-src-parameter">args</span>, **<span class="py-src-parameter">kw</span>):
+ ...
+</pre>
+
+ <p>Do whatever you can to reduce user confusion. It may also be
+ appropriate to <code class="python">assert</code> that the kwargs
+ dictionary does not contain parameters with names that will eventually
+ cause problems.</p>
+
+
+ <h2>Special Methods<a name="auto19"/></h2>
+
+ <p>The augmented assignment protocol, defined by <code class="python">__iadd__</code> and other
+ similarly named methods, can be used to allow objects to be modified in
+ place or to rebind names if an object is immutable -- both through use
+ of the same operator. This can lead to confusing code, which in turn
+ leads to buggy code. For this reason, methods of the augmented
+ assignment protocol should not be used in Twisted.</p>
+
+ <h2>Functions<a name="auto20"/></h2>
+
+ <p>Functions should be named similiarly to methods.</p>
+
+ <p>Functions or methods which are responding to events to
+ complete a callback or errback should be named <code>_cbMethodName</code> or
+ <code>_ebMethodName</code>, in order to distinguish them from normal
+ methods.</p>
+
+ <h2>Attributes<a name="auto21"/></h2>
+
+ <p>Attributes should be named similarly to functions and
+ methods. Attributes should be named descriptively; attribute
+ names like <code>mode</code>, <code>type</code>, and
+ <code>buf</code> are generally discouraged. Instead, use
+ <code>displayMode</code>, <code>playerType</code>, or
+ <code>inputBuffer</code>.</p>
+
+ <p>Do not use Python's <q>private</q> attribute syntax; prefix
+ non-public attributes with a single leading underscore. Since
+ several classes have the same name in Twisted, and they are
+ distinguished by which package they come from, Python's
+ double-underscore name mangling will not work reliably in some
+ cases. Also, name-mangled private variables are more difficult
+ to address when unit testing or persisting a class.</p>
+
+ <p>An attribute (or function, method or class) should be
+ considered private when one or more of the following conditions
+ are true:</p>
+
+ <ul>
+ <li>The attribute represents intermediate state which is not
+ always kept up-to-date.</li>
+
+ <li>Referring to the contents of the attribute or otherwise
+ maintaining a reference to it may cause resources to
+ leak.</li>
+
+ <li>Assigning to the attribute will break internal
+ assumptions.</li>
+
+ <li>The attribute is part of a known-to-be-sub-optimal
+ interface and will certainly be removed in a future
+ release.</li>
+ </ul>
+
+
+ <h2>Database<a name="auto22"/></h2>
+
+ <p>Database tables will be named with plural nouns.</p>
+
+ <p>Database columns will be named with underscores between
+ words, all lower case, since most databases do not distinguish
+ between case.</p>
+
+ <p>Any attribute, method argument, or method name that
+ corresponds <em>directly</em> to a column in the database will
+ be named exactly the same as that column, regardless of other
+ coding conventions surrounding that circumstance.</p>
+
+ <p>All SQL keywords should be in upper case.</p>
+
+ <h2>C Code<a name="auto23"/></h2>
+
+ <p>Wherever possible, C code should be optional, and the
+ default python implementation should be maintained in tandem
+ with it. C code should be strict ANSI C, and
+ <strong>must</strong> build using GCC as well as Visual Studio
+ for Windows, and really shouldn't have any problems with other
+ compilers either. Don't do anything tricky.</p>
+
+ <p>C code should only be used for efficiency, not for binding
+ to external libraries. If your particular code is not
+ frequently run, write it in Python. If you require the use of
+ an external library, develop a separate, external bindings
+ package and make your twisted code depend on it.</p>
+
+ <h2 id="commits">Commit Messages<a name="auto24"/></h2>
+
+ <p>The commit messages are being distributed in a myriad of ways. Because
+ of that, you need to observe a few simple rules when writing a commit
+ message.</p>
+
+ <p>The first line of the message is being used as both the subject of
+ the commit email and the announcement on #twisted. Therefore, it should
+ be short (aim for &lt; 80 characters) and descriptive -- and must be
+ able to stand alone (it is best if it is a complete sentence). The rest
+ of the e-mail should be separated with <em>hard line breaks</em> into
+ short lines (&lt; 70 characters). This is free-format, so you can do
+ whatever you like here.</p>
+
+ <p>Commit messages should be about <em>what</em>, not <em>how</em>: we can
+ get how from SVN diff. Explain reasons for commits, and what they
+ affect.</p>
+
+ <p>Each commit should be a single logical change, which is internally
+ consistent. If you can't summarize your changes in one short line, this
+ is probably a sign that they should be broken into multiple checkins.</p>
+
+ <h2>Source Control<a name="auto25"/></h2>
+
+ <p>Twisted currently uses Subversion for source control. All
+ development <strong>should</strong> occur using branches; when a task is
+ considered complete another Twisted developer may review it and if no
+ problems are found, it may be merged into trunk. The Twisted wiki has <a href="http://twistedmatrix.com/trac/wiki/TwistedDevelopment" shape="rect">a start</a>.
+ Branches <strong>must</strong> be used for major development. Branches
+ should be managed using <a href="http://divmod.org/trac/wiki/DivmodCombinator" shape="rect">Combinator</a> (but
+ if you can manage them in some other way without anyone noticing, knock
+ yourself out).</p>
+
+ <p>Certain features of Subversion should be avoided.</p>
+
+ <ul>
+ <li>
+
+ <p>Do not set the <code class="shell">svn:ignore</code> property on any
+ file or directory. What you wish to ignore, others may wish to examine.
+ What others may wish you ignore, <em>you</em> may wish you examine.
+ <code class="shell"> svn:ignore </code> will affect everyone who uses
+ the repository, and so it is not the right mechanism to express personal
+ preferences.</p>
+
+ <p>If you wish to ignore certain files use the <code class="shell">
+ global-ignores </code> feature of <code class="shell">
+ ~/.subversion/config </code>, for example:</p>
+
+ <pre class="shell" xml:space="preserve">
+[miscellany]
+global-ignores = dropin.cache *.pyc *.pyo *.o *.lo *.la #*# .*.rej *.rej .*~
+ </pre>
+
+ </li>
+ </ul>
+
+ <h2>Fallback<a name="auto26"/></h2>
+
+ <p>In case of conventions not enforced in this document, the reference
+ documents to use in fallback is
+ <a href="http://www.python.org/dev/peps/pep-0008/" shape="rect">PEP 8</a> for Python
+ code and <a href="http://www.python.org/dev/peps/pep-0007/" shape="rect">PEP 7</a> for
+ C code. For example, the paragraph <strong>Whitespace in Expressions and
+ Statements</strong> in PEP 8 describes what should be done in Twisted
+ code.</p>
+
+ <h2>Recommendations<a name="auto27"/></h2>
+
+ <p>These things aren't necessarily standardizeable (in that
+ code can't be easily checked for compliance) but are a good
+ idea to keep in mind while working on Twisted.</p>
+
+ <p>If you're going to work on a fragment of the Twisted
+ codebase, please consider finding a way that you would <em>use</em>
+ such a fragment in daily life. Using a Twisted Web server on your
+ website encourages you to actively maintain and improve your code,
+ as the little everyday issues with using it become apparent.</p>
+
+ <p>Twisted is a <strong>big</strong> codebase! If you're
+ refactoring something, please make sure to recursively grep for
+ the names of functions you're changing. You may be surprised to
+ learn where something is called. Especially if you are moving
+ or renaming a function, class, method, or module, make sure
+ that it won't instantly break other code.</p>
+
+ </div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/development/policy/doc-standard.html b/doc/core/development/policy/doc-standard.html
new file mode 100644
index 0000000..2eda492
--- /dev/null
+++ b/doc/core/development/policy/doc-standard.html
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: HTML Documentation Standard for Twisted</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">HTML Documentation Standard for Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Allowable Tags</a></li><li><a href="#auto1">Multi-line Code Snippets</a></li><ul><li><a href="#auto2">python</a></li><li><a href="#auto3">python-interpreter</a></li><li><a href="#auto4">shell</a></li></ul><li><a href="#auto5">Code inside paragraph text</a></li><li><a href="#auto6">Headers</a></li><li><a href="#auto7">XHTML</a></li><li><a href="#auto8">Tag Case</a></li><li><a href="#auto9">Footnotes</a></li><li><a href="#auto10">Suggestions</a></li><li><a href="#auto11">__all__</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Allowable Tags<a name="auto0"/></h2>
+
+ <p>Please try to restrict your HTML usage to the following tags (all only for the original logical purpose, and not whatever visual effect you see): <code>&lt;html&gt;</code>, <code>&lt;title&gt;</code>, <code>&lt;head&gt;</code>, <code>&lt;body&gt;</code>, <code>&lt;h1&gt;</code>, <code>&lt;h2</code>, <code>&lt;h3&gt;</code>, <code>&lt;ol&gt;</code>, <code>&lt;ul&gt;</code>, <code>&lt;dl&gt;</code>, <code>&lt;li&gt;</code>, <code>&lt;dt&gt;</code>, <code>&lt;dd&gt;</code>, <code>&lt;p&gt;</code>, <code>&lt;code&gt;</code>, <code>&lt;img&gt;</code>, <code>&lt;blockquote&gt;</code>, <code>&lt;a&gt;</code>, <code>&lt;cite&gt;</code>, <code>&lt;div&gt;</code>, <code>&lt;span&gt;</code>, <code>&lt;strong&gt;</code>, <code>&lt;em&gt;</code>, <code>&lt;pre&gt;</code>, <code>&lt;q&gt;</code>, <code>&lt;table&gt;</code>, <code>&lt;tr&gt;</code>, <code>&lt;td&gt;</code> and <code>&lt;th&gt;</code>.</p>
+
+ <p>Please avoid using the quote sign (<code>&quot;</code>) for quoting, and use the relevant html tags (<code>&lt;q&gt;&lt;/q&gt;</code>) -- it is impossible to distinguish right and left quotes with the quote sign, and some more sophisticated output methods work better with that distinction.</p>
+
+ <h2>Multi-line Code Snippets<a name="auto1"/></h2>
+
+ <p>Multi-line code snippets should be delimited with a
+ &lt;pre&gt; tag, with a mandatory <q>class</q> attribute. The
+ conventionalized classes are <q>python</q>, <q>python-interpreter</q>,
+ and <q>shell</q>. For example:</p>
+
+ <h3><q>python</q><a name="auto2"/></h3>
+ <p>Original markup:</p>
+ <blockquote>
+<pre xml:space="preserve">
+&lt;p&gt;
+For example, this is how one defines a Resource:
+&lt;/p&gt;
+
+&lt;pre class=&quot;python&quot;&gt;
+from twisted.web import resource
+
+class MyResource(resource.Resource):
+ def render_GET(self, request):
+ return &quot;Hello, world!&quot;
+&lt;/pre&gt;
+</pre>
+ </blockquote>
+
+ <p>Rendered result:</p>
+ <blockquote>
+ <p>For example, this is how one defines a Resource:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyResource</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Hello, world!&quot;</span>
+</pre>
+ </blockquote>
+
+ <p>Note that you should never have leading indentation inside a
+ &lt;pre&gt; block -- this makes it hard for readers to
+ copy/paste the code.</p>
+
+ <h3><q>python-interpreter</q><a name="auto3"/></h3>
+ <p>Original markup:</p>
+ <blockquote>
+<pre xml:space="preserve">
+&lt;pre class=&quot;python-interpreter&quot;&gt;
+&amp;gt;&amp;gt;&amp;gt; from twisted.web import resource
+&amp;gt;&amp;gt;&amp;gt; class MyResource(resource.Resource):
+... def render_GET(self, request):
+... return &quot;Hello, world!&quot;
+...
+&amp;gt;&amp;gt;&amp;gt; MyResource().render_GET(None)
+&quot;Hello, world!&quot;
+&lt;/pre&gt;
+</pre>
+ </blockquote>
+
+ <p>Rendered result:</p>
+ <blockquote>
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; from twisted.web import resource
+&gt;&gt;&gt; class MyResource(resource.Resource):
+... def render_GET(self, request):
+... return &quot;Hello, world!&quot;
+...
+&gt;&gt;&gt; MyResource().render_GET(None)
+&quot;Hello, world!&quot;
+</pre>
+ </blockquote>
+
+ <h3><q>shell</q><a name="auto4"/></h3>
+ <p>Original markup:</p>
+ <blockquote>
+<pre xml:space="preserve">
+ &lt;pre class=&quot;shell&quot;&gt;
+ $ twistd web --path /var/www
+ &lt;/pre&gt;
+</pre>
+ </blockquote>
+
+ <p>Rendered result:</p>
+ <blockquote>
+<pre class="shell" xml:space="preserve">
+$ twistd web --path /var/www
+</pre>
+ </blockquote>
+
+ <h2>Code inside paragraph text<a name="auto5"/></h2>
+
+ <p>For single-line code-snippets and attribute, method, class,
+ and module names, use the &lt;code&gt; tag, with a class of
+ <q>API</q> or <q>python</q>. During processing, module or class-names
+ with class <q>API</q> will automatically be looked up in the API
+ reference and have a link placed around it referencing the
+ actual API documents for that module/classname. If you wish to
+ reference an API document, then make sure you at least have a
+ single module-name so that the processing code will be able to
+ figure out which module or class you're referring to.</p>
+
+ <p>You may also use the <code>base</code> attribute in conjuction
+ with a class of <q>API</q> to indicate the module that should be prepended
+ to the module or classname. This is to help keep the documentation
+ clearer and less cluttered by allowing links to API docs that don't
+ need the module name.</p>
+ <p>Original markup:</p>
+ <blockquote>
+<pre xml:space="preserve">
+ &lt;p&gt;
+ To add a &lt;code class=&quot;API&quot;&gt;twisted.web.static.File&lt;/code&gt;
+ instance to a &lt;code class=&quot;API&quot;
+ base=&quot;twisted.web.resource&quot;&gt;Resource&lt;/code&gt; instance, do
+ &lt;code class=&quot;python&quot;&gt;myResource.putChild(&quot;resourcePath&quot;,
+ File(&quot;/tmp&quot;))&lt;/code&gt;.
+ &lt;/p&gt;
+
+</pre>
+ </blockquote>
+
+ <p>Rendered result:</p>
+ <blockquote>
+ <p>
+ To add a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.static.File.html" title="twisted.web.static.File">twisted.web.static.File</a></code>
+ instance to a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>
+ instance, do
+ <code class="python">myResource.putChild(&quot;resourcePath&quot;, File(&quot;/tmp&quot;))</code>.
+ </p>
+ </blockquote>
+
+ <h2>Headers<a name="auto6"/></h2>
+
+ <p>It goes without mentioning that you should use &lt;hN&gt; in
+ a sane way -- &lt;h1&gt; should only appear once in the
+ document, to specify the title. Sections of the document should
+ use &lt;h2&gt;, sub-headers &lt;h3&gt;, and so on.</p>
+
+ <h2>XHTML<a name="auto7"/></h2>
+
+ <p>XHTML is mandatory. That means tags that don't have a
+ closing tag need a <q>/</q>; for example, <code>&lt;hr /&gt;</code>
+ . Also, tags which have <q>optional</q> closing tags in HTML
+ <em>need</em> to be closed in XHTML; for example,
+ <code>&lt;li&gt;foo&lt;/li&gt;</code></p>
+
+ <h2>Tag Case<a name="auto8"/></h2>
+
+ <p>All tags will be done in lower-case. XHTML demands this, and
+ so do I. :-)</p>
+
+ <h2>Footnotes<a name="auto9"/></h2>
+
+ <p>Footnotes are enclosed inside
+ <code>&lt;span class=&quot;footnote&quot;&gt;&lt;/span&gt;</code>. They must not
+ contain any markup.</p>
+
+ <h2>Suggestions<a name="auto10"/></h2>
+
+ <p>Use <code class="shell">lore -o lint</code> to check your documentation
+ is not broken. <code class="shell">lore -o lint</code> will never change
+ your HTML, but it will complain if it doesn't like it.</p>
+
+ <p>Don't use tables for formatting. 'nuff said.</p>
+
+ <h2>__all__<a name="auto11"/></h2>
+
+ <p><code class="python">__all__</code> is a module level list of strings, naming
+ objects in the module that are public. Make sure publically exported classes,
+ functions and constants are listed here.</p>
+
+ </div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/development/policy/index.html b/doc/core/development/policy/index.html
new file mode 100644
index 0000000..d733b2a
--- /dev/null
+++ b/doc/core/development/policy/index.html
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Development Policy</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Development Policy</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<p>
+This series of documents is designed for people who wish to contribute to the
+Twisted codebase.
+</p>
+
+ <ul>
+ <li><a href="coding-standard.html" shape="rect">Coding standard</a></li>
+ <li><a href="doc-standard.html" shape="rect">Documentation standard</a></li>
+ <li><a href="writing-standard.html" shape="rect">Documentation writing standard</a></li>
+ <li><a href="test-standard.html" shape="rect">Testing standard</a></li>
+ <li><a href="svn-dev.html" shape="rect">Working from Twisted's Subversion
+ repository</a></li>
+ </ul>
+
+</div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/development/policy/svn-dev.html b/doc/core/development/policy/svn-dev.html
new file mode 100644
index 0000000..1e6c74c
--- /dev/null
+++ b/doc/core/development/policy/svn-dev.html
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Working from Twisted's Subversion repository</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Working from Twisted's Subversion repository</h1>
+ <div class="toc"><ol><li><a href="#auto0">Checkout</a></li><li><a href="#auto1">Alternate tree names</a></li><li><a href="#auto2">Combinator</a></li><li><a href="#auto3">Compiling C extensions</a></li><li><a href="#auto4">Running tests</a></li><li><a href="#auto5">Building docs</a></li><li><a href="#auto6">Committing and Post-commit Hooks</a></li><li><a href="#auto7">Emacs</a></li><li><a href="#auto8">Building Debian packages</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p>If you're going to be doing development on Twisted itself, or if you want
+to take advantage of bleeding-edge features (or bug fixes) that are not yet
+available in a numbered release, you'll probably want to check out a tree from
+the Twisted Subversion repository. The Trunk is where all current development
+takes place.</p>
+
+<p>This document lists some useful tips for working on this cutting
+edge.</p>
+
+<h2>Checkout<a name="auto0"/></h2>
+
+<p>Subversion tutorials can be found elsewhere, see in particular
+<a href="http://subversion.apache.org/" shape="rect">the Subversion homepage</a>. The
+relevant data you need to check out a copy of the Twisted tree is available on
+the <a href="http://twistedmatrix.com/trac/wiki/TwistedDevelopment" shape="rect">development
+page</a>, and is as follows:</p>
+
+<pre class="shell" xml:space="preserve">
+$ svn co svn://svn.twistedmatrix.com/svn/Twisted/trunk Twisted
+</pre>
+
+<h2>Alternate tree names<a name="auto1"/></h2>
+
+<p>By using <code>svn co svn://svn.twistedmatrix.com/svn/Twisted/trunk
+otherdir</code>, you can put the workspace tree in a directory other than
+<q>Twisted</q>. I do this (with a name like <q>Twisted-Subversion</q>) to
+remind myself that this tree comes from Subversion and not from a released
+version (like <q>Twisted-1.0.5</q>). This practice can cause a few problems,
+because there are a few places in the Twisted tree that need to know where
+the tree starts, so they can add it to <code>sys.path</code> without
+requiring the user manually set their PYTHONPATH. These functions walk the
+current directory up to the root, looking for a directory named
+<q>Twisted</q> (sometimes exactly that, sometimes with a
+<code>.startswith</code> test). Generally these are test scripts or other
+administrative tools which expect to be launched from somewhere inside the
+tree (but not necessarily from the top).</p>
+
+<p>If you rename the tree to something other than <code>Twisted</code>, these
+tools may wind up trying to use Twisted source files from /usr/lib/python2.5
+or elsewhere on the default <code>sys.path</code>. Normally this won't
+matter, but it is good to be aware of the issue in case you run into
+problems.</p>
+
+<p><code>twisted/test/process_twisted.py</code> is one of these programs.</p>
+
+<h2>Combinator<a name="auto2"/></h2>
+
+<p>In order to simplify the use of Subversion, we typically use
+<a href="http://twistedmatrix.com/trac/wiki/Combinator" shape="rect">Divmod Combinator</a>.
+You may find it to be useful, too. In particular, because Twisted uses
+branches for almost all feature development, if you plan to contribute to
+Twisted you will probably find Combinator very useful. For more details,
+see the Combinator website, as well as the
+<a href="http://twistedmatrix.com/trac/wiki/UltimateQualityDevelopmentSystem" shape="rect">
+UQDS</a> page.</p>
+
+<h2>Compiling C extensions<a name="auto3"/></h2>
+
+<p>
+There are currently several C extension modules in Twisted:
+<code class="python">twisted.internet.cfsupport</code>,
+<code class="python">twisted.internet.iocpreactor._iocp</code>,
+and <code class="python">twisted.python._epoll</code>. These modules
+are optional, but you'll have to compile them if you want to experience their
+features, performance improvements, or bugs. There are two approaches.
+</p>
+
+<p>The first is to do a regular distutils <code>./setup.py build</code>, which
+will create a directory under <code>build/</code> to hold both the generated
+<code>.so</code> files as well as a copy of the 600-odd <code>.py</code> files
+that make up Twisted. If you do this, you will need to set your PYTHONPATH to
+something like <code>MyDir/Twisted/build/lib.linux-i686-2.5</code> in order to
+run code against the Subversion twisted (as opposed to whatever's installed in
+<code>/usr/lib/python2.5</code> or wherever python usually looks). In
+addition, you will need to re-run the <code>build</code> command <em>every
+time</em> you change a <code>.py</code> file. The <code>build/lib.foo</code>
+directory is a copy of the main tree, and that copy is only updated when you
+re-run <code>setup.py build</code>. It is easy to forget this and then wonder
+why your code changes aren't being expressed.</p>
+
+<p>The second technique is to build the C modules in place, and point your
+PYTHONPATH at the top of the tree, like <code>MyDir/Twisted</code>. This way
+you're using the .py files in place too, removing the confusion a forgotten
+rebuild could cause with the separate build/ directory above. To build the C
+modules in place, do <code>./setup.py build_ext -i</code>. You only need to
+re-run this command when you change the C files. Note that
+<code>setup.py</code> is not Make, it does not always get the dependencies
+right (<code>.h</code> files in particular), so if you are hacking on the
+cReactor you may need to manually delete the <code>.o</code> files before
+doing a rebuild. Also note that doing a <code>setup.py clean</code> will
+remove the <code>.o</code> files but not the final <code>.so</code> files,
+they must be deleted by hand.</p>
+
+
+<h2>Running tests<a name="auto4"/></h2>
+
+<p>To run the full unit-test suite, do:</p>
+
+<pre class="shell" xml:space="preserve">./bin/trial twisted</pre>
+
+<p>To run a single test file (like <code>twisted/test/test_defer.py</code>),
+do one of:</p>
+
+<pre class="shell" xml:space="preserve">./bin/trial twisted.test.test_defer</pre>
+
+<p>or</p>
+
+<pre class="shell" xml:space="preserve">./bin/trial twisted/test/test_defer.py</pre>
+
+<p>To run any tests that are related to a code file, like
+<code>twisted/protocols/imap4.py</code>, do:</p>
+
+<pre class="shell" xml:space="preserve">./bin/trial --testmodule twisted/mail/imap4.py</pre>
+
+<p>This depends upon the <code>.py</code> file having an appropriate
+<q>test-case-name</q> tag that indicates which test cases provide coverage.
+See the <a href="test-standard.html" shape="rect">Test Standards</a> document for
+details about using <q>test-case-name</q>. In this example, the
+<code>twisted.mail.test.test_imap</code> test will be run.</p>
+
+<p>Many tests create temporary files in /tmp or ./_trial_temp, but
+everything in /tmp should be deleted when the test finishes. Sometimes these
+cleanup calls are commented out by mistake, so if you see a stray
+<code>/tmp/@12345.1</code> directory, it is probably from <code>test_dirdbm</code> or <code>test_popsicle</code>.
+Look for an <code>rmtree</code> that has been commented out and complain to
+the last developer who touched that file.</p>
+
+<h2>Building docs<a name="auto5"/></h2>
+
+<p>Twisted documentation (not including the automatically-generated API docs)
+is in <a href="http://twistedmatrix.com/trac/wiki/TwistedLore" shape="rect">Lore Format</a>.
+These <code>.xhtml</code> files are translated into <code>.html</code> files by
+the <q>bin/lore/lore</q> script, which can check the files for syntax problems
+(hlint), process multiple files at once, insert the files into a template
+before processing, and can also translate the files into LaTeX or PostScript
+instead.</p>
+
+<p>To build the HTML form of the howto/ docs, do the following. Note that
+the index file will be placed in <code>doc/core/howto/index.html</code>.</p>
+
+<pre class="shell" xml:space="preserve">
+./bin/lore/lore -p --config template=doc/core/howto/template.tpl doc/core/howto/*.xhtml
+</pre>
+
+<p>To run hlint over a single Lore document, such as
+<code>doc/development/policy/svn-dev.xhtml</code>, do the following. This is
+useful because the HTML conversion may bail without a useful explanation if
+it sees mismatched tags.</p>
+
+<pre class="shell" xml:space="preserve">
+./bin/lore/lore -n --output lint doc/development/policy/svn-dev.xhtml
+</pre>
+
+<p>To convert it to HTML (including markup, interpolation of examples,
+footnote processing, etc), do the following. The results will be placed in
+<code>doc/development/policy/svn-dev.html</code>:</p>
+
+<pre class="shell" xml:space="preserve">
+./bin/lore/lore -p --config template=doc/core/howto/template.tpl \
+ doc/development/policy/svn-dev.xhtml
+</pre>
+
+<p>Note that hyperlinks to other documents may not be quite right unless you
+include a <q>-l</q> argument to <code>bin/lore/lore</code>. Links in the
+.xhtml file are to .xhtml targets: when the .xhtml is turned into .html, the
+link targets are supposed to be turned into .html also. In addition to this,
+Lore markup of the form <code>&lt;code class=&quot;API&quot;&gt;</code> is supposed to
+turn into a link to the corresponding API reference page. These links will
+probably be wrong unless the correct base URL is provided to Lore.</p>
+
+<h2>Committing and Post-commit Hooks<a name="auto6"/></h2>
+
+<p>Twisted uses a customized
+<a href="http://bazaar.launchpad.net/~exarkun/twisted-trac-integration/trunk/annotate/head%3A/trac-hooks/trac-post-commit-hook" shape="rect">
+trac-post-commit-hook</a> to enable ticket updates based on svn commit
+logs. When making a branch for a ticket, the branch name should end
+in <code>-&lt;ticket number&gt;</code>, for
+example <code>my-branch-9999</code>. This will add a ticket comment containing a
+changeset link and branch name. To make your commit message show up as a comment
+on a Trac ticket, add a <code>refs #&lt;ticket number&gt;</code> line at the
+bottom of your commit message. To automatically close a ticket on Trac
+as <code>Fixed</code> and add a comment with the closing commit message, add
+a <code>Fixes: #&lt;ticket number&gt;</code> line to your commit message. In
+general, a commit message closing a ticket looks like this:</p>
+
+<pre xml:space="preserve">
+Merge my-branch-9999: A single-line summary.
+
+Author: jesstess
+Reviewers: exarkun, glyph
+Fixes: #9999
+
+My longer description of the changes made.
+</pre>
+
+<p>The <a href="coding-standard.html" shape="rect">Twisted Coding Standard</a>
+elaborates on commit messages and source control.</p>
+
+<h2>Emacs<a name="auto7"/></h2>
+
+<p>A minor mode for development with Twisted using Emacs is available. See
+<code>twisted-dev.el</code>, provided by <a href="http://launchpad.net/twisted-emacs" shape="rect">twisted-emacs</a>,
+for several utility functions which make it easier to grep for methods, run test cases, etc.</p>
+
+<h2>Building Debian packages<a name="auto8"/></h2>
+
+<p>Our support for building Debian packages has fallen into disrepair. We
+would very much like to restore this functionality, but until we do so, if
+you are interested in this, you are on your own. See
+<a href="http://github.com/astraw/stdeb" shape="rect">stdeb</a> for one possible approach to
+this.</p>
+
+</div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/development/policy/test-standard.html b/doc/core/development/policy/test-standard.html
new file mode 100644
index 0000000..9539356
--- /dev/null
+++ b/doc/core/development/policy/test-standard.html
@@ -0,0 +1,399 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Unit Tests in Twisted</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Unit Tests in Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Unit Tests in the Twisted Philosophy</a></li><li><a href="#auto1">What to Test, What Not to Test</a></li><li><a href="#auto2">Running the Tests</a></li><ul><li><a href="#auto3">How</a></li><li><a href="#auto4">When</a></li></ul><li><a href="#auto5">Adding a Test</a></li><li><a href="#auto6">Test Implementation Guidelines</a></li><ul><li><a href="#auto7">Real I/O</a></li><li><a href="#auto8">Real Time</a></li><li><a href="#auto9">The Global Reactor</a></li></ul><li><a href="#auto10">Skipping tests, TODO items</a></li><ul><li><a href="#auto11">.todo and Testing New Functionality </a></li><li><a href="#auto12">Line Coverage Information</a></li></ul><li><a href="#auto13">Associating Test Cases With Source Files</a></li><li><a href="#auto14">Links</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <p>Each <em>unit test</em> tests one bit of functionality in the
+ software. Unit tests are entirely automated and complete quickly.
+ Unit tests for the entire system are gathered into one test suite,
+ and may all be run in a single batch. The result of a unit test
+ is simple: either it passes, or it doesn't. All this means you
+ can test the entire system at any time without inconvenience, and
+ quickly see what passes and what fails.</p>
+
+ <h2>Unit Tests in the Twisted Philosophy<a name="auto0"/></h2>
+
+ <p>The Twisted development team adheres to the practice of <a href="http://c2.com/cgi/wiki?ExtremeProgramming" shape="rect">Extreme Programming</a> (XP),
+ and the usage of unit tests is a cornerstone XP practice. Unit tests are a
+ tool to give you increased confidence. You changed an algorithm -- did you
+ break something? Run the unit tests. If a test fails, you know where to
+ look, because each test covers only a small amount of code, and you know it
+ has something to do with the changes you just made. If all the tests pass,
+ you're good to go, and you don't need to second-guess yourself or worry that
+ you just accidentally broke someone else's program.</p>
+
+ <h2>What to Test, What Not to Test<a name="auto1"/></h2>
+
+ <blockquote><p>You don't have to write a test for every single
+ method you write, only production methods that could possibly break.</p>
+ </blockquote>
+
+ <p>-- Kent Beck, <cite>Extreme Programming Explained</cite>, p. 58.</p>
+
+ <h2>Running the Tests<a name="auto2"/></h2>
+
+ <h3>How<a name="auto3"/></h3>
+
+ <p>From the root of the Twisted source tree, run
+ <a href="http://twistedmatrix.com/trac/wiki/TwistedTrial" shape="rect">Trial</a>:
+ </p>
+
+ <pre class="shell" xml:space="preserve">
+$ bin/trial twisted
+ </pre>
+
+ <p>You'll find that having something like this in your emacs init
+ files is quite handy:</p>
+
+<pre class="elisp" xml:space="preserve">
+(defun runtests () (interactive)
+ (compile &quot;python /somepath/Twisted/bin/trial /somepath/Twisted&quot;))
+
+(global-set-key [(alt t)] 'runtests)
+</pre>
+ <h3>When<a name="auto4"/></h3>
+
+ <p>Always, always, <em>always</em> be sure <a href="http://www.xprogramming.com/xpmag/expUnitTestsAt100.htm" shape="rect">all the
+ tests pass</a> before committing any code. If someone else
+ checks out code at the start of a development session and finds
+ failing tests, they will not be happy and may decide to <em>hunt
+ you down</em>.</p>
+
+ <p>Since this is a geographically dispersed team, the person who can help
+ you get your code working probably isn't in the room with you. You may want
+ to share your work in progress over the network, but you want to leave the
+ main Subversion tree in good working order.
+ So <a href="http://svnbook.red-bean.com/en/1.0/ch04.html" shape="rect">use a branch</a>,
+ and merge your changes back in only after your problem is solved and all the
+ unit tests pass again.</p>
+
+ <h2>Adding a Test<a name="auto5"/></h2>
+
+ <p>Please don't add new modules to Twisted without adding tests
+ for them too. Otherwise we could change something which breaks
+ your module and not find out until later, making it hard to know
+ exactly what the change that broke it was, or until after a
+ release, and nobody wants broken code in a release.</p>
+
+ <p>Tests go into dedicated test packages such as
+ <code>twisted/test/</code> or <code>twisted/conch/test/</code>,
+ and are named <code>test_foo.py</code>, where <code>foo</code> is the name
+ of the module or package being tested. Extensive documentation on using
+ the PyUnit framework for writing unit tests can be found in the
+ <a href="#links" shape="rect">links section</a> below.
+ </p>
+
+ <p>One deviation from the standard PyUnit documentation: To ensure
+ that any variations in test results are due to variations in the
+ code or environment and not the test process itself, Twisted ships
+ with its own, compatible, testing framework. That just
+ means that when you import the unittest module, you will <code class="python">from twisted.trial import unittest</code> instead of the
+ standard <code class="python">import unittest</code>.</p>
+
+ <p>As long as you have followed the module naming and placement
+ conventions, <code class="shell">trial</code> will be smart
+ enough to pick up any new tests you write.</p>
+
+ <p>PyUnit provides a large number of assertion methods to be used when
+ writing tests. Many of these are redundant. For consistency, Twisted
+ unit tests should use the <code>assert</code> forms rather than the
+ <code>fail</code> forms. Also, use <code>assertEqual</code>,
+ <code>assertNotEqual</code>, and <code>assertAlmostEqual</code> rather
+ than <code>assertEquals</code>, <code>assertNotEquals</code>, and
+ <code>assertAlmostEquals</code>. <code>assertTrue</code> is also
+ preferred over <code>assert_</code>. You may notice this convention is
+ not followed everywhere in the Twisted codebase. If you are changing
+ some test code and notice the wrong method being used in nearby code,
+ feel free to adjust it.</p>
+
+ <p>When you add a unit test, make sure all methods have docstrings
+ specifying at a high level the intent of the test. That is, a description
+ that users of the method would understand.</p>
+
+ <h2>Test Implementation Guidelines<a name="auto6"/></h2>
+
+ <p>Here are some guidelines to follow when writing tests for the Twisted
+ test suite. Many tests predate these guidelines and so do not follow them.
+ When in doubt, follow the guidelines given here, not the example of old unit
+ tests.</p>
+
+ <h3>Real I/O<a name="auto7"/></h3>
+
+ <p>Most unit tests should avoid performing real, platform-implemented I/O
+ operations. Real I/O is slow, unreliable, and unwieldy. When implementing
+ a protocol, <code>twisted.test.proto_helpers.StringTransport</code> can be
+ used instead of a real TCP transport. <code>StringTransport</code> is fast,
+ deterministic, and can easily be used to exercise all possible network
+ behaviors.</p>
+
+ <h3>Real Time<a name="auto8"/></h3>
+
+ <p>Most unit tests should also avoid waiting for real time to pass. Unit
+ tests which construct and advance
+ a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.task.Clock.html" title="twisted.internet.task.Clock">twisted.internet.task.Clock</a></code> are fast and
+ deterministic.</p>
+
+ <h3>The Global Reactor<a name="auto9"/></h3>
+
+ <p>Since unit tests are avoiding real I/O and real time, they can usually
+ avoid using a real reactor. The only exceptions to this are unit tests for
+ a real reactor implementation. Unit tests for protocol implementations or
+ other application code should not use a reactor. Unit tests for real
+ reactor implementations should not use the global reactor, but should
+ instead use <code>twisted.internet.test.reactormixins.ReactorBuilder</code>
+ so they can be applied to all of the reactor implementations automatically.
+ In no case should new unit tests use the global reactor.</p>
+
+
+<h2>Skipping tests, TODO items<a name="auto10"/></h2>
+
+<p>Trial, the Twisted unit test framework, has some extensions which are
+designed to encourage developers to add new tests. One common situation is
+that a test exercises some optional functionality: maybe it depends upon
+certain external libraries being available, maybe it only works on certain
+operating systems. The important common factor is that nobody considers
+these limitations to be a bug.</p>
+
+<p>To make it easy to test as much as possible, some tests may be skipped in
+certain situations. Individual test cases can raise the
+<code>SkipTest</code> exception to indicate that they should be skipped, and
+the remainder of the test is not run. In the summary (the very last thing
+printed, at the bottom of the test output) the test is counted as a
+<q>skip</q> instead of a <q>success</q> or <q>fail</q>. This should be used
+inside a conditional which looks for the necessary prerequisites:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SSHClientTests</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_sshClient</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">ssh_path</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">unittest</span>.<span class="py-src-variable">SkipTest</span>(<span class="py-src-string">&quot;cannot find ssh, nothing to test&quot;</span>)
+ <span class="py-src-variable">foo</span>() <span class="py-src-comment"># do actual test after the SkipTest</span>
+</pre>
+
+<p>You can also set the <code>.skip</code> attribute on the method, with a
+string to indicate why the test is being skipped. This is convenient for
+temporarily turning off a test case, but it can also be set conditionally (by
+manipulating the class attributes after they've been defined):</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SomeThingTests</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_thing</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">dotest</span>()
+ <span class="py-src-variable">test_thing</span>.<span class="py-src-variable">skip</span> = <span class="py-src-string">&quot;disabled locally&quot;</span>
+</pre>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">MyTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_one</span>(<span class="py-src-parameter">self</span>):
+ ...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_thing</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">dotest</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">haveThing</span>:
+ <span class="py-src-variable">MyTestCase</span>.<span class="py-src-variable">test_thing</span>.<span class="py-src-variable">im_func</span>.<span class="py-src-variable">skip</span> = <span class="py-src-string">&quot;cannot test without Thing&quot;</span>
+ <span class="py-src-comment"># but test_one() will still run</span>
+</pre>
+
+<p>Finally, you can turn off an entire TestCase at once by setting the .skip
+attribute on the class. If you organize your tests by the functionality they
+depend upon, this is a convenient way to disable just the tests which cannot
+be run.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">TCPTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ ...
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SSLTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">haveSSL</span>:
+ <span class="py-src-variable">skip</span> = <span class="py-src-string">&quot;cannot test without SSL support&quot;</span>
+ <span class="py-src-comment"># but TCPTestCase will still run</span>
+ ...
+</pre>
+
+<h3>.todo and Testing New Functionality <a name="auto11"/></h3>
+
+<p>Two good practices which arise from the <q>XP</q> development process are
+sometimes at odds with each other:</p>
+
+<ul>
+ <li>Unit tests are a good thing. Good developers recoil in horror when
+ they see a failing unit test. They should drop everything until the test
+ has been fixed.</li>
+
+ <li>Good developers write the unit tests first. Once tests are done, they
+ write implementation code until the unit tests pass. Then they stop.</li>
+</ul>
+
+<p>These two goals will sometimes conflict. The unit tests that are written
+first, before any implementation has been done, are certain to fail. We want
+developers to commit their code frequently, for reliability and to improve
+coordination between multiple people working on the same problem together.
+While the code is being written, other developers (those not involved in the
+new feature) should not have to pay attention to failures in the new code.
+We should not dilute our well-indoctrinated Failing Test Horror Syndrome by
+crying wolf when an incomplete module has not yet started passing its unit
+tests. To do so would either teach the module author to put off writing or
+committing their unit tests until <em>after</em> all the functionality is
+working, or it would teach the other developers to ignore failing test
+cases. Both are bad things.</p>
+
+<p><q>.todo</q> is intended to solve this problem. When a developer first
+starts writing the unit tests for functionality that has not yet been
+implemented, they can set the <code>.todo</code> attribute on the test
+methods that are expected to fail. These methods will still be run, but
+their failure will not be counted the same as normal failures: they will go
+into an <q>expected failures</q> category. Developers should learn to treat
+this category as a second-priority queue, behind actual test failures.</p>
+
+<p>As the developer implements the feature, the tests will eventually start
+passing. This is surprising: after all those tests are marked as being
+expected to fail. The .todo tests which nevertheless pass are put into a
+<q>unexpected success</q> category. The developer should remove the .todo
+tag from these tests. At that point, they become normal tests, and their
+failure is once again cause for immediate action by the entire development
+team.</p>
+
+<p>The life cycle of a test is thus:</p>
+
+<ol>
+ <li>Test is created, marked <code>.todo</code>. Test fails: <q>expected
+ failure</q>.</li>
+
+ <li>Code is written, test starts to pass. <q>unexpected success</q>.</li>
+
+ <li><code>.todo</code> tag is removed. Test passes. <q>success</q>.</li>
+
+ <li>Code is broken, test starts to fail. <q>failure</q>. Developers spring
+ into action.</li>
+
+ <li>Code is fixed, test passes once more. <q>success</q>.</li>
+</ol>
+
+<p>Any test which remains marked with <code>.todo</code> for too long should
+be examined. Either it represents functionality which nobody is working on,
+or the test is broken in some fashion and needs to be fixed. Generally,
+<code>.todo</code> may be of use while you are developing a feature, but
+by the time you are ready to commit anything, all the tests you have written
+should be passing. In other words, you should rarely, if ever, feel the need
+to add a test marked todo to trunk. When you do, consider whether a ticket
+in the issue tracker would be more useful.</p>
+
+<h3>Line Coverage Information<a name="auto12"/></h3>
+
+<p>Trial provides line coverage information, which is very useful to ensure
+old code has decent coverage. Passing the <code>--coverage</code> option to
+to Trial will generate the coverage information in a file called
+<code>coverage</code> which can be found in the <code>_trial_temp</code>
+folder. This option requires Python 2.3.3 or newer.</p>
+
+<h2>Associating Test Cases With Source Files<a name="auto13"/></h2>
+
+<p>Please add a <code>test-case-name</code> tag to the source file that is
+covered by your new test. This is a comment at the beginning of the file
+which looks like one of the following:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-comment"># -*- test-case-name: twisted.test.test_defer -*-</span>
+</pre>
+
+<p>or</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+<span class="py-src-comment"># -*- test-case-name: twisted.test.test_defer -*-</span>
+</pre>
+
+<p>This format is understood by emacs to mark <q>File Variables</q>. The
+intention is to accept <code>test-case-name</code> anywhere emacs would on
+the first or second line of the file (but not in the <code>File
+Variables:</code> block that emacs accepts at the end of the file). If you
+need to define other emacs file variables, you can either put them in the
+<code>File Variables:</code> block or use a semicolon-separated list of
+variable definitions:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-comment"># -*- test-case-name: twisted.test.test_defer; fill-column: 75; -*-</span>
+</pre>
+
+<p>If the code is exercised by multiple test cases, those may be marked by
+using a comma-separated list of tests, as follows: (NOTE: not all tools can
+handle this yet.. <code>trial --testmodule</code> does, though)</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-comment"># -*- test-case-name: twisted.test.test_defer,twisted.test.test_tcp -*-</span>
+</pre>
+
+<p>The <code>test-case-name</code> tag will allow <code class="shell">trial
+--testmodule twisted/dir/myfile.py</code> to determine which test cases need
+to be run to exercise the code in <code>myfile.py</code>. Several tools (as
+well as <a href="http://launchpad.net/twisted-emacs" shape="rect"/>'s
+<code>twisted-dev.el</code>'s F9 command) use this to automatically
+run the right tests.</p>
+
+<h2 id="links">Links<a name="auto14"/></h2><a name="links" shape="rect"/>
+
+<ul>
+ <li>A chapter on <a href="http://diveintopython.org/unit_testing/index.html" shape="rect">Unit Testing</a>
+ in Mark Pilgrim's <a href="http://diveintopython.org" shape="rect">Dive Into
+ Python</a>.</li>
+
+ <li><a href="http://docs.python.org/library/unittest.html" shape="rect">unittest</a> module documentation, in the <a href="http://docs.python.org/library" shape="rect">Python Library
+ Reference</a>.</li>
+
+ <li><a href="http://c2.com/cgi/wiki?UnitTest" shape="rect">UnitTest</a> on
+ the <a href="http://c2.com/cgi/wiki" shape="rect">PortlandPatternRepository
+ Wiki</a>, where all the cool <a href="http://c2.com/cgi/wiki?ExtremeProgramming" shape="rect">ExtremeProgramming</a> kids hang out.</li>
+
+ <li><a href="http://www.extremeprogramming.org/rules/unittests.html" shape="rect">Unit
+ Tests</a> in <a href="http://www.extremeprogramming.org" shape="rect">Extreme Programming: A Gentle Introduction</a>.</li>
+
+ <li>Ron Jeffries expounds on the importance of <a href="http://www.xprogramming.com/xpmag/expUnitTestsAt100.htm" shape="rect">Unit
+ Tests at 100%</a>.</li>
+
+ <li>Ron Jeffries writes about the <a href="http://www.xprogramming.com/Practices/PracUnitTest.html" shape="rect">Unit
+ Test</a> in the <a href="http://www.xprogramming.com/Practices/xpractices.htm" shape="rect">Extreme
+ Programming practices of C3</a>.</li>
+
+ <li><a href="http://pyunit.sourceforge.net" shape="rect">PyUnit's homepage</a>.</li>
+
+ <li>The top-level tests directory, <a href="http://twistedmatrix.com/trac/browser/trunk/twisted/test" shape="rect">twisted/test</a>, in Subversion.</li>
+
+ </ul>
+
+ <p>See also <a href="../../howto/testing.html" shape="rect">Tips for writing tests for
+ Twisted code</a>.</p>
+
+ </div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/development/policy/writing-standard.html b/doc/core/development/policy/writing-standard.html
new file mode 100644
index 0000000..1bfaaa4
--- /dev/null
+++ b/doc/core/development/policy/writing-standard.html
@@ -0,0 +1,313 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Writing Standard</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Writing Standard</h1>
+ <div class="toc"><ol><li><a href="#auto0">General style</a></li><li><a href="#auto1">Evangelism and usage documents</a></li><li><a href="#auto2">Descriptions of features</a></li><li><a href="#auto3">Linking</a></li><li><a href="#auto4">Introductions</a></li><ul><li><a href="#auto5">Introductory paragraph</a></li><li><a href="#auto6">Description of target audience</a></li><li><a href="#auto7">Goals of document</a></li></ul><li><a href="#auto8">Example code</a></li><li><a href="#auto9">Conclusions</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <p>The Twisted writing standard describes the documentation writing
+ styles we prefer in our documentation. This standard applies particularly
+ to howtos and other descriptive documentation.</p>
+
+ <p>This document should be read with the <a href="doc-standard.html" shape="rect">documentation standard</a>, which describes
+ markup style for the documentation.</p>
+
+ <p>This document is meant to help Twisted documentation authors produce
+ documentation that does not have the following problems:</p>
+
+ <ul>
+ <li>misleads users about what is good Twisted style;</li>
+ <li>misleads users into thinking that an advanced howto is an introduction
+ to writing their first Twisted server; and</li>
+ <li>misleads users about whether they fit the document's target audience:
+ for example, that they are able to use enterprise without knowing how to
+ write SQL queries.</li>
+ </ul>
+
+ <h2>General style<a name="auto0"/></h2>
+
+ <p>Documents should aim to be clear and concise, allowing the API
+ documentation and the example code to tell as much of the story as they
+ can. Demonstrations and where necessary supported arguments should always
+ preferred to simple statements (&quot;here is how you would simplify this
+ code with Deferreds&quot; rather than &quot;Deferreds make code
+ simpler&quot;).</p>
+
+ <p>Documents should be clearly delineated into sections and subsections.
+ Each of these sections, like the overall document, should have a single
+ clear purpose. This is most easily tested by trying to have meaningful
+ headings: a section which is headed by &quot;More details&quot; or
+ &quot;Advanced stuff&quot; is not purposeful enough. There should be
+ fairly obvious ways to split a document. The two most common are task
+ based sectioning and sectioning which follows module and class
+ separations.</p>
+
+ <p>Documentation must use American English spelling, and where possible
+ avoid any local variants of either vocabulary or grammar. Grammatically
+ complex sentences should ideally be avoided: these make reading
+ unnecessarily difficult, particularly for non-native speakers.</p>
+
+ <h2>Evangelism and usage documents<a name="auto1"/></h2>
+
+ <p>The Twisted documentation should maintain a reasonable distinction
+ between &quot;evangelism&quot; documentation, which compares the Twisted
+ design or Twisted best practice with other approaches and argues for the
+ Twisted approach, and &quot;usage&quot; documentation, which describes the
+ Twisted approach in detail without comparison to other possible
+ approaches.</p>
+
+ <p>While both kinds of documentation are useful, they have different
+ audiences. The first kind of document, evangelical documents, is useful to
+ a reader who is researching and comparing approaches and seeking to
+ understand the Twisted approach or Twisted functionality in order to
+ decide whether it is useful to them. The second kind of document, usage
+ documents, are useful to a reader who has decided to use Twisted and
+ simply wants further information about available functions and
+ architectures they can use to accomplish their goal.</p>
+
+ <p>Since they have distinct audiences, evangelism and detailed usage
+ documentation belongs in separate files. There should be links between
+ them in 'Further reading' or similar sections.</p>
+
+ <h2>Descriptions of features<a name="auto2"/></h2>
+
+ <p>Descriptions of any feature added since release 2.0 of Twisted core
+ must have a note describing which release of which Twisted project they
+ were added in at the first mention in each document. If they are not yet
+ released, give them the number of the next minor release.</p>
+
+ <p>For example, a substantial change might have a version number added in
+ the introduction:</p>
+
+ <blockquote>
+ This document describes the Application infrastructure for deploying
+ Twisted applications <em>(added in Twisted 1.3)</em>.
+ </blockquote>
+
+ <p>The version does not need to be mentioned elsewhere in the document
+ except for specific features which were added in subsequent releases,
+ which might should be mentioned separately.</p>
+
+ <blockquote>
+ The simplest way to create a <code>.tac</code> file, SuperTac <em>(added
+ in Twisted Core 99.7)</em>...</blockquote>
+
+ <p>In the case where the usage of a feature has substantially changed, the
+ number should be that of the release in which the current usage became
+ available. For example:</p>
+
+ <blockquote> This document describes the Application infrastructure for
+ deploying Twisted applications <em>(updated[/substantially updated] in Twisted
+ 2.7)</em>. </blockquote>
+
+ <h2>Linking<a name="auto3"/></h2>
+
+ <p>The first occurrence of the name of any module, class or function should
+ always link to the API documents. Subsequent mentions may or may not link
+ at the author's discretion: discussions which are very closely bound to a
+ particular API should probably link in the first mention in the given
+ section.</p>
+
+ <p>Links between howtos are encouraged. Overview documents and tutorials
+ should always link to reference documents and in depth documents. These
+ documents should link among themselves wherever it's needed: if you're
+ tempted to re-describe the functionality of another module, you should
+ certainly link instead.</p>
+
+ <h2>Introductions<a name="auto4"/></h2>
+
+ <p>The introductory section of a Twisted howto should immediately follow
+ the top-level heading and precede any subheadings.</p>
+
+ <p>The following items should be present in the introduction to Twisted
+ howtos: the introductory paragraph and the description of the target
+ audience.</p>
+
+ <h3>Introductory paragraph<a name="auto5"/></h3>
+
+ <p>The introductory paragraph of a document should summarize what the
+ document is designed to present. It should use the both proper names for
+ the Twisted technologies and simple non-Twisted descriptions of the
+ technologies. For example, in this paragraph both the name of the technology
+ (&quot;Conch&quot;) and a description (&quot;SSH server&quot;) are used:</p>
+
+ <blockquote>
+ This document describes setting up a SSH server to serve data from the
+ file system using Conch, the Twisted SSH implementation.
+ </blockquote>
+
+ <p>The introductory paragraph should be relatively short, but should, like
+ the above, somewhere define the document's objective: what the reader
+ should be able to do using instructions in the document.</p>
+
+ <h3>Description of target audience<a name="auto6"/></h3>
+
+ <p>Subsequent paragraphs in the introduction should describe the target
+ audience of the document: who would want to read it, and what they should
+ know before they can expect to use your document. For example:</p>
+
+ <blockquote>
+ <p>
+ The target audience of this document is a Twisted user who has a set of
+ filesystem like data objects that they would like to make available to
+ authenticated users over SFTP.
+ </p>
+
+ <p>
+ Following the directions in this document will require that you are
+ familiar with managing authentication via the Twisted Cred system.
+ </p>
+ </blockquote>
+
+ <p>Use your discretion about the extent to which you list assumed
+ knowledge. Very introductory documents that are going to be among a
+ reader's first exposure to Twisted will even need to specify that they
+ rely on knowledge of Python and of certain networking concepts (ports,
+ servers, clients, connections) but documents that are going to be sought
+ out by existing Twisted users for particular purposes only need to specify
+ other Twisted knowledge that is assumed.</p>
+
+ <p>Any knowledge of technologies that wouldn't be considered &quot;core
+ Python&quot; and/or &quot;simple networking&quot; need to be explicitly
+ specified, no matter how obvious they seem to someone familiar with the
+ technology. For example, it needs to be stated that someone using
+ enterprise should know SQL and should know how to set up and populate
+ databases for testing purposes.</p>
+
+ <p>Where possible, link to other documents that will fill in missing
+ knowledge for the reader. Linking to documents in the Twisted repository
+ is preferred but not essential.</p>
+
+ <h3>Goals of document<a name="auto7"/></h3>
+
+ <p>The introduction should finish with a list of tasks that the user can
+ expect to see the document accomplish. These tasks should be concrete
+ rather than abstract, so rather than telling the user that they will
+ &quot;understand Twisted Conch&quot;, you would list the specific tasks
+ that they will see the document do. For example:</p>
+
+ <blockquote>
+ <p>
+ This document will demonstrate the following tasks using Twisted Conch:
+ </p>
+
+ <ul>
+ <li>creating an anonymous access read-only SFTP server using a filesystem
+ backend;</li>
+ <li>creating an anonymous access read-only SFTP server using a proxy
+ backend connecting to an HTTP server; and</li>
+ <li>creating a anonymous access read and write SFTP server using a
+ filesystem backend.</li>
+ </ul>
+ </blockquote>
+
+ <p>In many cases this will essentially be a list of your code examples,
+ but it need not be. If large sections of your code are devoted to design
+ discussions, your goals might resemble the following:</p>
+
+ <blockquote>
+ <p>
+ This document will discuss the following design aspects of writing Conch
+ servers:
+ </p>
+
+ <ul>
+ <li>authentication of users; and</li>
+ <li>choice of data backends.</li>
+ </ul>
+ </blockquote>
+
+
+ <h2>Example code<a name="auto8"/></h2>
+
+ <p>Wherever possible, example code should be provided to illustrate a
+ certain technique or piece of functionality.</p>
+
+ <p>Example code should try and meet as many of the following requirements
+ as possible:</p>
+
+ <ul>
+ <li>example code should be a complete working example suitable for copying
+ and pasting and running by the reader (where possible, provide a link to a
+ file to download);</li>
+ <li>example code should be short;</li>
+ <li>example code should be commented very extensively, with the assumption
+ that this code may be read by a Twisted newcomer;</li>
+ <li>example code should conform to the <a href="coding-standard.html" shape="rect">coding standard</a>; and</li>
+ <li>example code should exhibit 'best practice', not only for dealing with
+ the target functionality, but also for use of the application framework
+ and so on.</li>
+ </ul>
+
+ <p>The requirement to have a complete working example will occasionally
+ impose upon authors the need to have a few dummy functions: in Twisted
+ documentation the most common example is where a function is needed to
+ generate a Deferred and fire it after some time has passed. An example
+ might be this, where <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.task.deferLater.html" title="twisted.internet.task.deferLater">deferLater</a></code> is used to fire a callback
+ after a period of time:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">task</span>, <span class="py-src-variable">reactor</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getDummyDeferred</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Dummy method which returns a deferred that will fire in 5 seconds with
+ a result
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">task</span>.<span class="py-src-variable">deferLater</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">5</span>, <span class="py-src-keyword">lambda</span> <span class="py-src-variable">x</span>: <span class="py-src-string">&quot;RESULT&quot;</span>)
+</pre>
+
+ <p>As in the above example, it is imperative to clearly mark that the
+ function is a dummy in as many ways as you can: using <code>Dummy</code> in
+ the function name, explaining that it is a dummy in the docstring, and
+ marking particular lines as being required to create an effect for the
+ purposes of demonstration. In most cases, this will save the reader from
+ mistaking this dummy method for an idiom they should use in their Twisted
+ code.</p>
+
+ <h2>Conclusions<a name="auto9"/></h2>
+
+ <p>The conclusion of a howto should follow the very last section heading
+ in a file. This heading would usually be called &quot;Conclusion&quot;.</p>
+
+ <p>The conclusion of a howto should remind the reader of the tasks that
+ they have done while reading the document. For example:</p>
+
+ <blockquote>
+ <p>
+ In this document, you have seen how to:
+ </p>
+
+ <ol>
+ <li>set up an anonymous read-only SFTP server;</li>
+ <li>set up a SFTP server where users authenticate;</li>
+ <li>set up a SFTP server where users are restricted to some parts of the
+ filesystem based on authentication; and</li>
+ <li>set up a SFTP server where users have write access to some parts of
+ the filesystem based on authentication.</li>
+ </ol>
+ </blockquote>
+
+ <p>If appropriate, the howto could follow this description with links to
+ other documents that might be of interest to the reader with their
+ newfound knowledge. However, these links should be limited to fairly
+ obvious extensions of at least one of the listed tasks.</p>
+
+ </div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/development/security.html b/doc/core/development/security.html
new file mode 100644
index 0000000..78286d1
--- /dev/null
+++ b/doc/core/development/security.html
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Security</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Security</h1>
+ <div class="toc"><ol><li><a href="#auto0">Bad input</a></li><li><a href="#auto1">Resource Exhaustion and DoS</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<p>We need to do a full audit of Twisted, module by module.
+This document list the sort of things you want to look for
+when doing this, or when writing your own code.</p>
+
+<h2>Bad input<a name="auto0"/></h2>
+
+<p>Any place we receive untrusted data, we need to be careful.
+In some cases we are not careful enough. For example, in HTTP
+there are many places where strings need to be converted to
+ints, so we use <code class="python">int()</code>. The problem
+is that this well accept negative numbers as well, whereas
+the protocol should only be accepting positive numbers.</p>
+
+<h2>Resource Exhaustion and DoS<a name="auto1"/></h2>
+
+<p>Make sure we never allow users to create arbitarily large
+strings or files. Some of the protocols still have issues
+like this. Place a limit which allows reasonable use but
+will cut off huge requests, and allow changing of this limit.
+</p>
+
+<p>Another operation to look out for are exceptions. They can fill
+up logs and take a lot of CPU time to render in web pages.</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/examples/ampclient.py b/doc/core/examples/ampclient.py
new file mode 100644
index 0000000..5349448
--- /dev/null
+++ b/doc/core/examples/ampclient.py
@@ -0,0 +1,26 @@
+from twisted.internet import reactor, defer
+from twisted.internet.protocol import ClientCreator
+from twisted.protocols import amp
+from ampserver import Sum, Divide
+
+
+def doMath():
+ d1 = ClientCreator(reactor, amp.AMP).connectTCP(
+ '127.0.0.1', 1234).addCallback(
+ lambda p: p.callRemote(Sum, a=13, b=81)).addCallback(
+ lambda result: result['total'])
+ def trapZero(result):
+ result.trap(ZeroDivisionError)
+ print "Divided by zero: returning INF"
+ return 1e1000
+ d2 = ClientCreator(reactor, amp.AMP).connectTCP(
+ '127.0.0.1', 1234).addCallback(
+ lambda p: p.callRemote(Divide, numerator=1234,
+ denominator=0)).addErrback(trapZero)
+ def done(result):
+ print 'Done with math:', result
+ defer.DeferredList([d1, d2]).addCallback(done)
+
+if __name__ == '__main__':
+ doMath()
+ reactor.run()
diff --git a/doc/core/examples/ampserver.py b/doc/core/examples/ampserver.py
new file mode 100644
index 0000000..7b5adf0
--- /dev/null
+++ b/doc/core/examples/ampserver.py
@@ -0,0 +1,40 @@
+from twisted.protocols import amp
+
+class Sum(amp.Command):
+ arguments = [('a', amp.Integer()),
+ ('b', amp.Integer())]
+ response = [('total', amp.Integer())]
+
+
+class Divide(amp.Command):
+ arguments = [('numerator', amp.Integer()),
+ ('denominator', amp.Integer())]
+ response = [('result', amp.Float())]
+ errors = {ZeroDivisionError: 'ZERO_DIVISION'}
+
+
+class Math(amp.AMP):
+ def sum(self, a, b):
+ total = a + b
+ print 'Did a sum: %d + %d = %d' % (a, b, total)
+ return {'total': total}
+ Sum.responder(sum)
+
+ def divide(self, numerator, denominator):
+ result = float(numerator) / denominator
+ print 'Divided: %d / %d = %f' % (numerator, denominator, result)
+ return {'result': result}
+ Divide.responder(divide)
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.internet.protocol import Factory
+ pf = Factory()
+ pf.protocol = Math
+ reactor.listenTCP(1234, pf)
+ print 'started'
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/bananabench.py b/doc/core/examples/bananabench.py
new file mode 100644
index 0000000..a712afa
--- /dev/null
+++ b/doc/core/examples/bananabench.py
@@ -0,0 +1,79 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import sys
+import time
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+# Twisted Imports
+from twisted.spread import banana
+from twisted.internet import protocol
+
+iterationCount = 10000
+
+class BananaBench:
+ r = range( iterationCount )
+ def setUp(self, encClass):
+ self.io = StringIO.StringIO()
+ self.enc = encClass()
+ self.enc.makeConnection(protocol.FileWrapper(self.io))
+ self.enc._selectDialect("none")
+ self.enc.expressionReceived = self.putResult
+
+ def putResult(self, result):
+ self.result = result
+
+ def tearDown(self):
+ self.enc.connectionLost()
+ del self.enc
+
+ def testEncode(self, value):
+ starttime = time.time()
+ for i in self.r:
+ self.enc.sendEncoded(value)
+ self.io.truncate(0)
+ endtime = time.time()
+ print ' Encode took %s seconds' % (endtime - starttime)
+ return endtime - starttime
+
+ def testDecode(self, value):
+ self.enc.sendEncoded(value)
+ encoded = self.io.getvalue()
+ starttime = time.time()
+ for i in self.r:
+ self.enc.dataReceived(encoded)
+ endtime = time.time()
+ print ' Decode took %s seconds' % (endtime - starttime)
+ return endtime - starttime
+
+ def performTest(self, method, data, encClass):
+ self.setUp(encClass)
+ method(data)
+ self.tearDown()
+
+ def runTests(self, testData):
+ print 'Test data is: %s' % testData
+ print ' Using Pure Python Banana:'
+ self.performTest(self.testEncode, testData, banana.Banana)
+ self.performTest(self.testDecode, testData, banana.Banana)
+
+bench = BananaBench()
+print 'Doing %s iterations of each test.' % iterationCount
+print ''
+testData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+bench.runTests(testData)
+testData = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
+bench.runTests(testData)
+testData = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
+bench.runTests(testData)
+testData = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]
+bench.runTests(testData)
+testData = [1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l, 9l, 10l]
+bench.runTests(testData)
+testData = [1, 2, [3, 4], [30.5, 40.2], 5, ["six", "seven", ["eight", 9]], [10], []]
+bench.runTests(testData)
+
diff --git a/doc/core/examples/chatserver.py b/doc/core/examples/chatserver.py
new file mode 100644
index 0000000..76c3cf8
--- /dev/null
+++ b/doc/core/examples/chatserver.py
@@ -0,0 +1,37 @@
+"""The most basic chat protocol possible.
+
+run me with twistd -y chatserver.py, and then connect with multiple
+telnet clients to port 1025
+"""
+
+from twisted.protocols import basic
+
+
+
+class MyChat(basic.LineReceiver):
+ def connectionMade(self):
+ print "Got new client!"
+ self.factory.clients.append(self)
+
+ def connectionLost(self, reason):
+ print "Lost a client!"
+ self.factory.clients.remove(self)
+
+ def lineReceived(self, line):
+ print "received", repr(line)
+ for c in self.factory.clients:
+ c.message(line)
+
+ def message(self, message):
+ self.transport.write(message + '\n')
+
+
+from twisted.internet import protocol
+from twisted.application import service, internet
+
+factory = protocol.ServerFactory()
+factory.protocol = MyChat
+factory.clients = []
+
+application = service.Application("chatserver")
+internet.TCPServer(1025, factory).setServiceParent(application)
diff --git a/doc/core/examples/courier.py b/doc/core/examples/courier.py
new file mode 100644
index 0000000..db80a15
--- /dev/null
+++ b/doc/core/examples/courier.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example of a interfacing to Courier's mail filter interface.
+"""
+
+LOGFILE = '/tmp/filter.log'
+
+# Setup log file
+from twisted.python import log
+log.startLogging(open(LOGFILE, 'a'))
+import sys
+sys.stderr = log.logfile
+
+# Twisted imports
+from twisted.internet import reactor, stdio
+from twisted.internet.protocol import Protocol, Factory
+from twisted.protocols import basic
+
+FILTERS='/var/lib/courier/filters'
+ALLFILTERS='/var/lib/courier/allfilters'
+FILTERNAME='twistedfilter'
+
+import os, os.path
+from syslog import syslog, openlog, LOG_MAIL
+from rfc822 import Message
+
+def trace_dump():
+ t,v,tb = sys.exc_info()
+ openlog(FILTERNAME, 0, LOG_MAIL)
+ syslog('Unhandled exception: %s - %s' % (v, t))
+ while tb:
+ syslog('Trace: %s:%s %s' % (tb.tb_frame.f_code.co_filename,tb.tb_frame.f_code.co_name,tb.tb_lineno))
+ tb = tb.tb_next
+ # just to be safe
+ del tb
+
+def safe_del(file):
+ try:
+ if os.path.isdir(file):
+ os.removedirs(file)
+ else:
+ os.remove(file)
+ except OSError:
+ pass
+
+
+class DieWhenLost(Protocol):
+ def connectionLost(self, reason=None):
+ reactor.stop()
+
+
+class MailProcessor(basic.LineReceiver):
+ """I process a mail message.
+
+ Override filterMessage to do any filtering you want."""
+ messageFilename = None
+ delimiter = '\n'
+
+ def connectionMade(self):
+ log.msg('Connection from %r' % self.transport)
+ self.state = 'connected'
+ self.metaInfo = []
+
+ def lineReceived(self, line):
+ if self.state == 'connected':
+ self.messageFilename = line
+ self.state = 'gotMessageFilename'
+ if self.state == 'gotMessageFilename':
+ if line:
+ self.metaInfo.append(line)
+ else:
+ if not self.metaInfo:
+ self.transport.loseConnection()
+ return
+ self.filterMessage()
+
+ def filterMessage(self):
+ """Override this.
+
+ A trivial example is included.
+ """
+ try:
+ m = Message(open(self.messageFilename))
+ self.sendLine('200 Ok')
+ except:
+ trace_dump()
+ self.sendLine('435 %s processing error' % FILTERNAME)
+
+
+def main():
+ # Listen on the UNIX socket
+ f = Factory()
+ f.protocol = MailProcessor
+ safe_del('%s/%s' % (ALLFILTERS, FILTERNAME))
+ reactor.listenUNIX('%s/%s' % (ALLFILTERS, FILTERNAME), f, 10)
+
+ # Once started, close fd 3 to let Courier know we're ready
+ reactor.callLater(0, os.close, 3)
+
+ # When stdin is closed, it's time to exit.
+ s = stdio.StandardIO(DieWhenLost())
+
+ # Go!
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/cred.py b/doc/core/examples/cred.py
new file mode 100644
index 0000000..6fabc9a
--- /dev/null
+++ b/doc/core/examples/cred.py
@@ -0,0 +1,163 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+
+import sys
+from zope.interface import implements, Interface
+
+from twisted.protocols import basic
+from twisted.internet import protocol
+from twisted.python import log
+
+from twisted.cred import error
+from twisted.cred import portal
+from twisted.cred import checkers
+from twisted.cred import credentials
+
+class IProtocolUser(Interface):
+ def getPrivileges():
+ """Return a list of privileges this user has."""
+
+ def logout():
+ """Cleanup per-login resources allocated to this avatar"""
+
+class AnonymousUser:
+ implements(IProtocolUser)
+
+ def getPrivileges(self):
+ return [1, 2, 3]
+
+ def logout(self):
+ print "Cleaning up anonymous user resources"
+
+class RegularUser:
+ implements(IProtocolUser)
+
+ def getPrivileges(self):
+ return [1, 2, 3, 5, 6]
+
+ def logout(self):
+ print "Cleaning up regular user resources"
+
+class Administrator:
+ implements(IProtocolUser)
+
+ def getPrivileges(self):
+ return range(50)
+
+ def logout(self):
+ print "Cleaning up administrator resources"
+
+class Protocol(basic.LineReceiver):
+ user = None
+ portal = None
+ avatar = None
+ logout = None
+
+ def connectionMade(self):
+ self.sendLine("Login with USER <name> followed by PASS <password> or ANON")
+ self.sendLine("Check privileges with PRIVS")
+
+ def connectionLost(self, reason):
+ if self.logout:
+ self.logout()
+ self.avatar = None
+ self.logout = None
+
+ def lineReceived(self, line):
+ f = getattr(self, 'cmd_' + line.upper().split()[0])
+ if f:
+ try:
+ f(*line.split()[1:])
+ except TypeError:
+ self.sendLine("Wrong number of arguments.")
+ except:
+ self.sendLine("Server error (probably your fault)")
+
+ def cmd_ANON(self):
+ if self.portal:
+ self.portal.login(credentials.Anonymous(), None, IProtocolUser
+ ).addCallbacks(self._cbLogin, self._ebLogin
+ )
+ else:
+ self.sendLine("DENIED")
+
+ def cmd_USER(self, name):
+ self.user = name
+ self.sendLine("Alright. Now PASS?")
+
+ def cmd_PASS(self, password):
+ if not self.user:
+ self.sendLine("USER required before PASS")
+ else:
+ if self.portal:
+ self.portal.login(
+ credentials.UsernamePassword(self.user, password),
+ None,
+ IProtocolUser
+ ).addCallbacks(self._cbLogin, self._ebLogin
+ )
+ else:
+ self.sendLine("DENIED")
+
+ def cmd_PRIVS(self):
+ self.sendLine("You have the following privileges: ")
+ self.sendLine(" ".join(map(str, self.avatar.getPrivileges())))
+
+ def _cbLogin(self, (interface, avatar, logout)):
+ assert interface is IProtocolUser
+ self.avatar = avatar
+ self.logout = logout
+ self.sendLine("Login successful. Available commands: PRIVS")
+
+ def _ebLogin(self, failure):
+ failure.trap(error.UnauthorizedLogin)
+ self.sendLine("Login denied! Go away.")
+
+class ServerFactory(protocol.ServerFactory):
+ protocol = Protocol
+
+ def __init__(self, portal):
+ self.portal = portal
+
+ def buildProtocol(self, addr):
+ p = protocol.ServerFactory.buildProtocol(self, addr)
+ p.portal = self.portal
+ return p
+
+class Realm:
+ implements(portal.IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if IProtocolUser in interfaces:
+ if avatarId == checkers.ANONYMOUS:
+ av = AnonymousUser()
+ elif avatarId.isupper():
+ # Capitalized usernames are administrators.
+ av = Administrator()
+ else:
+ av = RegularUser()
+ return IProtocolUser, av, av.logout
+ raise NotImplementedError("Only IProtocolUser interface is supported by this realm")
+
+def main():
+ r = Realm()
+ p = portal.Portal(r)
+ c = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ c.addUser("auser", "thepass")
+ c.addUser("SECONDUSER", "secret")
+ p.registerChecker(c)
+ p.registerChecker(checkers.AllowAnonymousAccess())
+
+ f = ServerFactory(p)
+
+ log.startLogging(sys.stdout)
+
+ from twisted.internet import reactor
+ reactor.listenTCP(4738, f)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/dbcred.py b/doc/core/examples/dbcred.py
new file mode 100755
index 0000000..c1c216e
--- /dev/null
+++ b/doc/core/examples/dbcred.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Simple example of a db checker: define a L{ICredentialsChecker} implementation
+that deals with a database backend to authenticate a user.
+"""
+
+from twisted.cred import error
+from twisted.cred.credentials import IUsernameHashedPassword, IUsernamePassword
+from twisted.cred.checkers import ICredentialsChecker
+from twisted.internet.defer import Deferred
+
+from zope.interface import implements
+
+
+class DBCredentialsChecker(object):
+ """
+ This class checks the credentials of incoming connections
+ against a user table in a database.
+ """
+ implements(ICredentialsChecker)
+
+ def __init__(self, runQuery,
+ query="SELECT username, password FROM user WHERE username = %s",
+ customCheckFunc=None, caseSensitivePasswords=True):
+ """
+ @param runQuery: This will be called to get the info from the db.
+ Generally you'd want to create a
+ L{twisted.enterprice.adbapi.ConnectionPool} and pass it's runQuery
+ method here. Otherwise pass a function with the same prototype.
+ @type runQuery: C{callable}
+
+ @type query: query used to authenticate user.
+ @param query: C{str}
+
+ @param customCheckFunc: Use this if the passwords in the db are stored
+ as hashes. We'll just call this, so you can do the checking
+ yourself. It takes the following params:
+ (username, suppliedPass, dbPass) and must return a boolean.
+ @type customCheckFunc: C{callable}
+
+ @param caseSensitivePasswords: If true requires that every letter in
+ C{credentials.password} is exactly the same case as the it's
+ counterpart letter in the database.
+ This is only relevant if C{customCheckFunc} is not used.
+ @type caseSensitivePasswords: C{bool}
+ """
+ self.runQuery = runQuery
+ self.caseSensitivePasswords = caseSensitivePasswords
+ self.customCheckFunc = customCheckFunc
+ # We can't support hashed password credentials if we only have a hash
+ # in the DB
+ if customCheckFunc:
+ self.credentialInterfaces = (IUsernamePassword,)
+ else:
+ self.credentialInterfaces = (
+ IUsernamePassword, IUsernameHashedPassword,)
+
+ self.sql = query
+
+ def requestAvatarId(self, credentials):
+ """
+ Authenticates the kiosk against the database.
+ """
+ # Check that the credentials instance implements at least one of our
+ # interfaces
+ for interface in self.credentialInterfaces:
+ if interface.providedBy(credentials):
+ break
+ else:
+ raise error.UnhandledCredentials()
+ # Ask the database for the username and password
+ dbDeferred = self.runQuery(self.sql, (credentials.username,))
+ # Setup our deferred result
+ deferred = Deferred()
+ dbDeferred.addCallbacks(self._cbAuthenticate, self._ebAuthenticate,
+ callbackArgs=(credentials, deferred),
+ errbackArgs=(credentials, deferred))
+ return deferred
+
+ def _cbAuthenticate(self, result, credentials, deferred):
+ """
+ Checks to see if authentication was good. Called once the info has
+ been retrieved from the DB.
+ """
+ if len(result) == 0:
+ # Username not found in db
+ deferred.errback(error.UnauthorizedLogin('Username unknown'))
+ else:
+ username, password = result[0]
+ if self.customCheckFunc:
+ # Let the owner do the checking
+ if self.customCheckFunc(
+ username, credentials.password, password):
+ deferred.callback(credentials.username)
+ else:
+ deferred.errback(
+ error.UnauthorizedLogin('Password mismatch'))
+ else:
+ # It's up to us or the credentials object to do the checking
+ # now
+ if IUsernameHashedPassword.providedBy(credentials):
+ # Let the hashed password checker do the checking
+ if credentials.checkPassword(password):
+ deferred.callback(credentials.username)
+ else:
+ deferred.errback(
+ error.UnauthorizedLogin('Password mismatch'))
+ elif IUsernamePassword.providedBy(credentials):
+ # Compare the passwords, deciging whether or not to use
+ # case sensitivity
+ if self.caseSensitivePasswords:
+ passOk = (
+ password.lower() == credentials.password.lower())
+ else:
+ passOk = password == credentials.password
+ # See if they match
+ if passOk:
+ deferred.callback(credentials.username)
+ else:
+ deferred.errback(
+ error.UnauthorizedLogin('Password mismatch'))
+ else:
+ # OK, we don't know how to check this
+ deferred.errback(error.UnhandledCredentials())
+
+ def _ebAuthenticate(self, message, credentials, deferred):
+ """
+ The database lookup failed for some reason.
+ """
+ deferred.errback(error.LoginFailed(message))
+
+
+def main():
+ """
+ Run a simple echo pb server to test the checker. It defines a custom query
+ for dealing with sqlite special quoting, but otherwise it's a
+ straightforward use of the object.
+
+ You can test it running C{pbechoclient.py}.
+ """
+ import sys
+ from twisted.python import log
+ log.startLogging(sys.stdout)
+ import os
+ if os.path.isfile('testcred'):
+ os.remove('testcred')
+ from twisted.enterprise import adbapi
+ pool = adbapi.ConnectionPool('pysqlite2.dbapi2', 'testcred')
+ # Create the table that will be used
+ query1 = """CREATE TABLE user (
+ username string,
+ password string
+ )"""
+ # Insert a test user
+ query2 = """INSERT INTO user VALUES ('guest', 'guest')"""
+ def cb(res):
+ pool.runQuery(query2)
+ pool.runQuery(query1).addCallback(cb)
+
+ checker = DBCredentialsChecker(pool.runQuery,
+ query="SELECT username, password FROM user WHERE username = ?")
+ from twisted.cred.portal import Portal
+
+ import pbecho
+ from twisted.spread import pb
+ portal = Portal(pbecho.SimpleRealm())
+ portal.registerChecker(checker)
+ reactor.listenTCP(pb.portno, pb.PBServerFactory(portal))
+
+
+if __name__ == "__main__":
+ from twisted.internet import reactor
+ reactor.callWhenRunning(main)
+ reactor.run()
+
diff --git a/doc/core/examples/echoclient.py b/doc/core/examples/echoclient.py
new file mode 100644
index 0000000..6bb6750
--- /dev/null
+++ b/doc/core/examples/echoclient.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet.protocol import ClientFactory
+from twisted.protocols.basic import LineReceiver
+from twisted.internet import reactor
+import sys
+
+class EchoClient(LineReceiver):
+ end="Bye-bye!"
+ def connectionMade(self):
+ self.sendLine("Hello, world!")
+ self.sendLine("What a fine day it is.")
+ self.sendLine(self.end)
+
+ def lineReceived(self, line):
+ print "receive:", line
+ if line==self.end:
+ self.transport.loseConnection()
+
+class EchoClientFactory(ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ print 'connection failed:', reason.getErrorMessage()
+ reactor.stop()
+
+ def clientConnectionLost(self, connector, reason):
+ print 'connection lost:', reason.getErrorMessage()
+ reactor.stop()
+
+def main():
+ factory = EchoClientFactory()
+ reactor.connectTCP('localhost', 8000, factory)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/echoclient_ssl.py b/doc/core/examples/echoclient_ssl.py
new file mode 100755
index 0000000..d905cd5
--- /dev/null
+++ b/doc/core/examples/echoclient_ssl.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from OpenSSL import SSL
+import sys
+
+from twisted.internet.protocol import ClientFactory
+from twisted.protocols.basic import LineReceiver
+from twisted.internet import ssl, reactor
+
+
+class EchoClient(LineReceiver):
+ end="Bye-bye!"
+ def connectionMade(self):
+ self.sendLine("Hello, world!")
+ self.sendLine("What a fine day it is.")
+ self.sendLine(self.end)
+
+ def connectionLost(self, reason):
+ print 'connection lost (protocol)'
+
+ def lineReceived(self, line):
+ print "receive:", line
+ if line==self.end:
+ self.transport.loseConnection()
+
+class EchoClientFactory(ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ print 'connection failed:', reason.getErrorMessage()
+ reactor.stop()
+
+ def clientConnectionLost(self, connector, reason):
+ print 'connection lost:', reason.getErrorMessage()
+ reactor.stop()
+
+def main():
+ factory = EchoClientFactory()
+ reactor.connectSSL('localhost', 8000, factory, ssl.ClientContextFactory())
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/echoclient_udp.py b/doc/core/examples/echoclient_udp.py
new file mode 100644
index 0000000..93589a1
--- /dev/null
+++ b/doc/core/examples/echoclient_udp.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+class EchoClientDatagramProtocol(DatagramProtocol):
+ strings = [
+ "Hello, world!",
+ "What a fine day it is.",
+ "Bye-bye!"
+ ]
+
+ def startProtocol(self):
+ self.transport.connect('127.0.0.1', 8000)
+ self.sendDatagram()
+
+ def sendDatagram(self):
+ if len(self.strings):
+ datagram = self.strings.pop(0)
+ self.transport.write(datagram)
+ else:
+ reactor.stop()
+
+ def datagramReceived(self, datagram, host):
+ print 'Datagram received: ', repr(datagram)
+ self.sendDatagram()
+
+def main():
+ protocol = EchoClientDatagramProtocol()
+ t = reactor.listenUDP(0, protocol)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/echoserv.py b/doc/core/examples/echoserv.py
new file mode 100644
index 0000000..023a4e3
--- /dev/null
+++ b/doc/core/examples/echoserv.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet.protocol import Protocol, Factory
+from twisted.internet import reactor
+
+### Protocol Implementation
+
+# This is just about the simplest possible protocol
+class Echo(Protocol):
+ def dataReceived(self, data):
+ """
+ As soon as any data is received, write it back.
+ """
+ self.transport.write(data)
+
+
+def main():
+ f = Factory()
+ f.protocol = Echo
+ reactor.listenTCP(8000, f)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/echoserv_ssl.py b/doc/core/examples/echoserv_ssl.py
new file mode 100644
index 0000000..5037b92
--- /dev/null
+++ b/doc/core/examples/echoserv_ssl.py
@@ -0,0 +1,30 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from OpenSSL import SSL
+
+class ServerContextFactory:
+
+ def getContext(self):
+ """Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'."""
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file('server.pem')
+ ctx.use_privatekey_file('server.pem')
+ return ctx
+
+
+if __name__ == '__main__':
+ import echoserv, sys
+ from twisted.internet.protocol import Factory
+ from twisted.internet import ssl, reactor
+ from twisted.python import log
+ log.startLogging(sys.stdout)
+ factory = Factory()
+ factory.protocol = echoserv.Echo
+ reactor.listenSSL(8000, factory, ServerContextFactory())
+ reactor.run()
diff --git a/doc/core/examples/echoserv_udp.py b/doc/core/examples/echoserv_udp.py
new file mode 100644
index 0000000..5d9eaa4
--- /dev/null
+++ b/doc/core/examples/echoserv_udp.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+# Here's a UDP version of the simplest possible protocol
+class EchoUDP(DatagramProtocol):
+ def datagramReceived(self, datagram, address):
+ self.transport.write(datagram, address)
+
+def main():
+ reactor.listenUDP(8000, EchoUDP())
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/filewatch.py b/doc/core/examples/filewatch.py
new file mode 100644
index 0000000..19a0373
--- /dev/null
+++ b/doc/core/examples/filewatch.py
@@ -0,0 +1,17 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.application import internet
+
+def watch(fp):
+ fp.seek(fp.tell())
+ for line in fp.readlines():
+ sys.stdout.write(line)
+
+import sys
+from twisted.internet import reactor
+s = internet.TimerService(0.1, watch, file(sys.argv[1]))
+s.startService()
+reactor.run()
+s.stopService()
diff --git a/doc/core/examples/ftpclient.py b/doc/core/examples/ftpclient.py
new file mode 100644
index 0000000..c911888
--- /dev/null
+++ b/doc/core/examples/ftpclient.py
@@ -0,0 +1,113 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of using the FTP client
+"""
+
+# Twisted imports
+from twisted.protocols.ftp import FTPClient, FTPFileListProtocol
+from twisted.internet.protocol import Protocol, ClientCreator
+from twisted.python import usage
+from twisted.internet import reactor
+
+# Standard library imports
+import string
+import sys
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+
+class BufferingProtocol(Protocol):
+ """Simple utility class that holds all data written to it in a buffer."""
+ def __init__(self):
+ self.buffer = StringIO()
+
+ def dataReceived(self, data):
+ self.buffer.write(data)
+
+# Define some callbacks
+
+def success(response):
+ print 'Success! Got response:'
+ print '---'
+ if response is None:
+ print None
+ else:
+ print string.join(response, '\n')
+ print '---'
+
+
+def fail(error):
+ print 'Failed. Error was:'
+ print error
+
+def showFiles(result, fileListProtocol):
+ print 'Processed file listing:'
+ for file in fileListProtocol.files:
+ print ' %s: %d bytes, %s' \
+ % (file['filename'], file['size'], file['date'])
+ print 'Total: %d files' % (len(fileListProtocol.files))
+
+def showBuffer(result, bufferProtocol):
+ print 'Got data:'
+ print bufferProtocol.buffer.getvalue()
+
+
+class Options(usage.Options):
+ optParameters = [['host', 'h', 'localhost'],
+ ['port', 'p', 21],
+ ['username', 'u', 'anonymous'],
+ ['password', None, 'twisted@'],
+ ['passive', None, 0],
+ ['debug', 'd', 1],
+ ]
+
+def run():
+ # Get config
+ config = Options()
+ config.parseOptions()
+ config.opts['port'] = int(config.opts['port'])
+ config.opts['passive'] = int(config.opts['passive'])
+ config.opts['debug'] = int(config.opts['debug'])
+
+ # Create the client
+ FTPClient.debug = config.opts['debug']
+ creator = ClientCreator(reactor, FTPClient, config.opts['username'],
+ config.opts['password'], passive=config.opts['passive'])
+ creator.connectTCP(config.opts['host'], config.opts['port']).addCallback(connectionMade).addErrback(connectionFailed)
+ reactor.run()
+
+def connectionFailed(f):
+ print "Connection Failed:", f
+ reactor.stop()
+
+def connectionMade(ftpClient):
+ # Get the current working directory
+ ftpClient.pwd().addCallbacks(success, fail)
+
+ # Get a detailed listing of the current directory
+ fileList = FTPFileListProtocol()
+ d = ftpClient.list('.', fileList)
+ d.addCallbacks(showFiles, fail, callbackArgs=(fileList,))
+
+ # Change to the parent directory
+ ftpClient.cdup().addCallbacks(success, fail)
+
+ # Create a buffer
+ proto = BufferingProtocol()
+
+ # Get short listing of current directory, and quit when done
+ d = ftpClient.nlst('.', proto)
+ d.addCallbacks(showBuffer, fail, callbackArgs=(proto,))
+ d.addCallback(lambda result: reactor.stop())
+
+
+# this only runs if the module was *not* imported
+if __name__ == '__main__':
+ run()
+
diff --git a/doc/core/examples/ftpserver.py b/doc/core/examples/ftpserver.py
new file mode 100644
index 0000000..8c4588b
--- /dev/null
+++ b/doc/core/examples/ftpserver.py
@@ -0,0 +1,55 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An example FTP server with minimal user authentication.
+"""
+
+from twisted.protocols.ftp import FTPFactory, FTPRealm
+from twisted.cred.portal import Portal
+from twisted.cred.checkers import AllowAnonymousAccess, FilePasswordDB
+from twisted.internet import reactor
+
+#
+# First, set up a portal (twisted.cred.portal.Portal). This will be used
+# to authenticate user logins, including anonymous logins.
+#
+# Part of this will be to establish the "realm" of the server - the most
+# important task in this case is to establish where anonymous users will
+# have default access to. In a real world scenario this would typically
+# point to something like '/pub' but for this example it is pointed at the
+# current working directory.
+#
+# The other important part of the portal setup is to point it to a list of
+# credential checkers. In this case, the first of these is used to grant
+# access to anonymous users and is relatively simple; the second is a very
+# primitive password checker. This example uses a plain text password file
+# that has one username:password pair per line. This checker *does* provide
+# a hashing interface, and one would normally want to use it instead of
+# plain text storage for anything remotely resembling a 'live' network. In
+# this case, the file "pass.dat" is used, and stored in the same directory
+# as the server. BAD.
+#
+# Create a pass.dat file which looks like this:
+#
+# =====================
+# jeff:bozo
+# grimmtooth:bozo2
+# =====================
+#
+p = Portal(FTPRealm('./'),
+ [AllowAnonymousAccess(), FilePasswordDB("pass.dat")])
+
+#
+# Once the portal is set up, start up the FTPFactory and pass the portal to
+# it on startup. FTPFactory will start up a twisted.protocols.ftp.FTP()
+# handler for each incoming OPEN request. Business as usual in Twisted land.
+#
+f = FTPFactory(p)
+
+#
+# You know this part. Point the reactor to port 21 coupled with the above factory,
+# and start the event loop.
+#
+reactor.listenTCP(21, f)
+reactor.run()
diff --git a/doc/core/examples/gpsfix.py b/doc/core/examples/gpsfix.py
new file mode 100644
index 0000000..eefb578
--- /dev/null
+++ b/doc/core/examples/gpsfix.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+GPSTest is a simple example using the SerialPort transport and the NMEA 0183
+and Rockwell Zodiac GPS protocols to display fix data as it is received from
+the device.
+"""
+from twisted.python import log, usage
+import sys
+
+if sys.platform == 'win32':
+ from twisted.internet import win32eventreactor
+ win32eventreactor.install()
+
+
+class GPSFixLogger:
+ def handle_fix(self, *args):
+ """
+ handle_fix gets called whenever either rockwell.Zodiac or nmea.NMEAReceiver
+ receives and decodes fix data. Generally, GPS receivers will report a
+ fix at 1hz. Implementing only this method is sufficient for most purposes
+ unless tracking of ground speed, course, utc date, or detailed satellite
+ information is necessary.
+
+ For example, plotting a map from MapQuest or a similar service only
+ requires longitude and latitude.
+ """
+ log.msg('fix:\n' +
+ '\n'.join(map(lambda n: ' %s = %s' % tuple(n), zip(('utc', 'lon', 'lat', 'fix', 'sat', 'hdp', 'alt', 'geo', 'dgp'), map(repr, args)))))
+
+class GPSOptions(usage.Options):
+ optFlags = [
+ ['zodiac', 'z', 'Use Rockwell Zodiac (DeLorme Earthmate) [default: NMEA 0183]'],
+ ]
+ optParameters = [
+ ['outfile', 'o', None, 'Logfile [default: sys.stdout]'],
+ ['baudrate', 'b', None, 'Serial baudrate [default: 4800 for NMEA, 9600 for Zodiac]'],
+ ['port', 'p', '/dev/ttyS0', 'Serial Port device'],
+ ]
+
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ from twisted.internet.serialport import SerialPort
+
+ o = GPSOptions()
+ try:
+ o.parseOptions()
+ except usage.UsageError, errortext:
+ print '%s: %s' % (sys.argv[0], errortext)
+ print '%s: Try --help for usage details.' % (sys.argv[0])
+ raise SystemExit, 1
+
+ logFile = o.opts['outfile']
+ if logFile is None:
+ logFile = sys.stdout
+ log.startLogging(logFile)
+
+ if o.opts['zodiac']:
+ from twisted.protocols.gps.rockwell import Zodiac as GPSProtocolBase
+ baudrate = 9600
+ else:
+ from twisted.protocols.gps.nmea import NMEAReceiver as GPSProtocolBase
+ baudrate = 4800
+ class GPSTest(GPSProtocolBase, GPSFixLogger):
+ pass
+
+ if o.opts['baudrate']:
+ baudrate = int(o.opts['baudrate'])
+
+
+ port = o.opts['port']
+ log.msg('Attempting to open %s at %dbps as a %s device' % (port, baudrate, GPSProtocolBase.__name__))
+ s = SerialPort(GPSTest(), o.opts['port'], reactor, baudrate=baudrate)
+ reactor.run()
diff --git a/doc/core/examples/index.html b/doc/core/examples/index.html
new file mode 100644
index 0000000..118e28d
--- /dev/null
+++ b/doc/core/examples/index.html
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">Simple Echo server and client</a></li><li><a href="#auto1">Chat</a></li><li><a href="#auto2">Echo server &amp; client variants</a></li><li><a href="#auto3">AMP server &amp; client variants</a></li><li><a href="#auto4">Perspective Broker</a></li><li><a href="#auto5">Cred</a></li><li><a href="#auto6">GUI</a></li><li><a href="#auto7">FTP examples</a></li><li><a href="#auto8">Logging</a></li><li><a href="#auto9">POSIX Specific Tricks</a></li><li><a href="#auto10">Miscellaneous</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Simple Echo server and client<a name="auto0"/></h2>
+ <ul>
+ <li><a href="simpleclient.py" shape="rect">simpleclient.py</a> - simple TCP client</li>
+ <li><a href="simpleserv.py" shape="rect">simpleserv.py</a> - simple TCP echo server</li>
+ </ul>
+
+ <h2>Chat<a name="auto1"/></h2>
+ <ul>
+ <li><a href="chatserver.py" shape="rect">chatserver.py</a> - shows how to communicate between clients</li>
+ </ul>
+
+ <h2>Echo server &amp; client variants<a name="auto2"/></h2>
+ <ul>
+ <li><a href="echoserv.py" shape="rect">echoserv.py</a> - variant on a simple TCP echo server</li>
+ <li><a href="echoclient.py" shape="rect">echoclient.py</a> - variant on a simple TCP client</li>
+ <li><a href="echoserv_udp.py" shape="rect">echoserv_udp.py</a> - simplest possible
+ UDP server</li>
+ <li><a href="echoclient_udp.py" shape="rect">echoclient_udp.py</a> - simple UDP
+ client</li>
+ <li><a href="echoserv_ssl.py" shape="rect">echoserv_ssl.py</a> - simple SSL server</li>
+ <li><a href="echoclient_ssl.py" shape="rect">echoclient_ssl.py</a> - simple SSL client</li>
+ </ul>
+
+ <h2>AMP server &amp; client variants<a name="auto3"/></h2>
+ <ul>
+ <li><a href="ampserver.py" shape="rect">ampserver.py</a> - do math using AMP</li>
+ <li><a href="ampclient.py" shape="rect">ampclient.py</a> - do math using AMP</li>
+ </ul>
+
+ <h2>Perspective Broker<a name="auto4"/></h2>
+ <ul>
+ <li><a href="pbsimple.py" shape="rect">pbsimple.py</a> - simplest possible PB server</li>
+ <li><a href="pbsimpleclient.py" shape="rect">pbsimpleclient.py</a> - simplest possible PB
+ client</li>
+ <li><a href="pbbenchclient.py" shape="rect">pbbenchclient.py</a> - benchmarking client</li>
+ <li><a href="pbbenchserver.py" shape="rect">pbbenchserver.py</a> - benchmarking server</li>
+ <li><a href="pbecho.py" shape="rect">pbecho.py</a> - echo server that uses login</li>
+ <li><a href="pbechoclient.py" shape="rect">pbechoclient.py</a> - echo client using login</li>
+ <li><a href="pb_exceptions.py" shape="rect">pb_exceptions.py</a> - example of exceptions over PB</li>
+ <li><a href="pbgtk2.py" shape="rect">pbgtk2.py</a> - example of using GTK2 with PB</li>
+ <li><a href="pbinterop.py" shape="rect">pbinterop.py</a> - shows off various types supported by PB</li>
+ <li><a href="bananabench.py" shape="rect">bananabench.py</a> - benchmark for banana</li>
+ </ul>
+
+ <h2>Cred<a name="auto5"/></h2>
+ <ul>
+ <li><a href="cred.py" shape="rect">cred.py</a> - Authenticate a user with an in-memory username/password
+ database</li>
+ <li><a href="dbcred.py" shape="rect">dbcred.py</a> - Using a database backend to authenticate a user</li>
+ </ul>
+
+ <h2>GUI<a name="auto6"/></h2>
+ <ul>
+ <li><a href="wxdemo.py" shape="rect">wxdemo.py</a> - demo of wxPython integration with Twisted</li>
+ <li><a href="pbgtk2.py" shape="rect">pbgtk2.py</a> - example of using GTK2 with PB</li>
+ <li><a href="pyuidemo.py" shape="rect">pyuidemo.py</a> - PyUI</li>
+ </ul>
+
+ <h2>FTP examples<a name="auto7"/></h2>
+ <ul>
+ <li><a href="ftpclient.py" shape="rect">ftpclient.py</a> - example of using the FTP client</li>
+ <li><a href="ftpserver.py" shape="rect">ftpserver.py</a> - create an FTP server which
+ serves files for anonymous users from the working directory and serves
+ files for authenticated users from <code class="shell">/home</code>.</li>
+ </ul>
+
+ <h2>Logging<a name="auto8"/></h2>
+ <ul>
+ <li><a href="twistd-logging.tac" shape="rect">twistd-logging.tac</a> - logging example using
+ ILogObserver</li>
+ <li><a href="testlogging.py" shape="rect">testlogging.py</a> - use twisted.python.log to log errors to
+ standard out</li>
+ <li><a href="rotatinglog.py" shape="rect">rotatinglog.py</a> - example of log file rotation</li>
+ </ul>
+
+ <h2>POSIX Specific Tricks<a name="auto9"/></h2>
+ <ul>
+ <li><a href="sendfd.py" shape="rect">sendfd.py</a>, <a href="recvfd.py" shape="rect">recvfd.py</a> - send and receive
+ file descriptors over UNIX domain sockets
+ </li>
+ </ul>
+
+ <h2>Miscellaneous<a name="auto10"/></h2>
+ <ul>
+ <li><a href="shaper.py" shape="rect">shaper.py</a> - example of rate-limiting your web server</li>
+ <li><a href="stdiodemo.py" shape="rect">stdiodemo.py</a> - example using stdio, Deferreds, LineReceiver
+ and twisted.web.client.</li>
+ <li><a href="mouse.py" shape="rect">mouse.py</a> - example using MouseMan protocol with the SerialPort
+ transport</li>
+ <li><a href="ptyserv.py" shape="rect">ptyserv.py</a> - serve shells in pseudo-terminals over TCP</li>
+ <li><a href="courier.py" shape="rect">courier.py</a> - example of interfacing to Courier's mail filter
+ interface</li>
+ <li><a href="longex.py" shape="rect">longex.py</a> - example of doing arbitarily long calculations nicely
+ in Twisted</li>
+ <li><a href="longex2.py" shape="rect">longex2.py</a> - using generators to do long calculations</li>
+ <li><a href="stdin.py" shape="rect">stdin.py</a> - reading a line at a time from standard input
+ without blocking the reactor</li>
+ <li><a href="streaming.py" shape="rect">streaming.py</a> - example of a push producer/consumer system</li>
+ <li><a href="filewatch.py" shape="rect">filewatch.py</a> - write the content of a file to standard out
+ one line at a time</li>
+ <li><a href="shoutcast.py" shape="rect">shoutcast.py</a> - example Shoutcast client</li>
+ <li><a href="gpsfix.py" shape="rect">gpsfix.py</a> - example using the SerialPort transport and GPS
+ protocols to display fix data as it is received from the device</li>
+ <li><a href="wxacceptance.py" shape="rect">wxacceptance.py</a> - acceptance tests for wxreactor</li>
+ <li><a href="postfix.py" shape="rect">postfix.py</a> - test application for PostfixTCPMapServer</li>
+ </ul>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/examples/longex.py b/doc/core/examples/longex.py
new file mode 100644
index 0000000..6fc9a7f
--- /dev/null
+++ b/doc/core/examples/longex.py
@@ -0,0 +1,66 @@
+"""Simple example of doing arbitarily long calculations nicely in Twisted.
+
+This is also a simple demonstration of twisted.protocols.basic.LineReceiver.
+"""
+
+from twisted.protocols import basic
+from twisted.internet import reactor
+from twisted.internet.protocol import ServerFactory
+
+class LongMultiplicationProtocol(basic.LineReceiver):
+ """A protocol for doing long multiplications.
+
+ It receives a list of numbers (seperated by whitespace) on a line, and
+ writes back the answer. The answer is calculated in chunks, so no one
+ calculation should block for long enough to matter.
+ """
+ def connectionMade(self):
+ self.workQueue = []
+
+ def lineReceived(self, line):
+ try:
+ numbers = map(long, line.split())
+ except ValueError:
+ self.sendLine('Error.')
+ return
+
+ if len(numbers) <= 1:
+ self.sendLine('Error.')
+ return
+
+ self.workQueue.append(numbers)
+ reactor.callLater(0, self.calcChunk)
+
+ def calcChunk(self):
+ # Make sure there's some work left; when multiple lines are received
+ # while processing is going on, multiple calls to reactor.callLater()
+ # can happen between calls to calcChunk().
+ if self.workQueue:
+ # Get the first bit of work off the queue
+ work = self.workQueue[0]
+
+ # Do a chunk of work: [a, b, c, ...] -> [a*b, c, ...]
+ work[:2] = [work[0] * work[1]]
+
+ # If this piece of work now has only one element, send it.
+ if len(work) == 1:
+ self.sendLine(str(work[0]))
+ del self.workQueue[0]
+
+ # Schedule this function to do more work, if there's still work
+ # to be done.
+ if self.workQueue:
+ reactor.callLater(0, self.calcChunk)
+
+
+class LongMultiplicationFactory(ServerFactory):
+ protocol = LongMultiplicationProtocol
+
+
+if __name__ == '__main__':
+ from twisted.python import log
+ import sys
+ log.startLogging(sys.stdout)
+ reactor.listenTCP(1234, LongMultiplicationFactory())
+ reactor.run()
+
diff --git a/doc/core/examples/longex2.py b/doc/core/examples/longex2.py
new file mode 100644
index 0000000..8758988
--- /dev/null
+++ b/doc/core/examples/longex2.py
@@ -0,0 +1,101 @@
+"""Example of doing arbitarily long calculations nicely in Twisted.
+
+This is also a simple demonstration of twisted.protocols.basic.LineReceiver.
+This example uses generators to do the calculation. It also tries to be
+a good example in division of responsibilities:
+- The protocol handles the wire layer, reading in lists of numbers
+ and writing out the result.
+- The factory decides on policy, and has relatively little knowledge
+ of the details of the protocol. Other protocols can use the same
+ factory class by intantiating and setting .protocol
+- The factory does little job itself: it is mostly a policy maker.
+ The 'smarts' are in free-standing functions which are written
+ for flexibility.
+
+The goal is for minimal dependencies:
+- You can use runIterator to run any iterator inside the Twisted
+ main loop.
+- You can use multiply whenever you need some way of multiplying
+ numbers such that the multiplications will happen asynchronously,
+ but it is your responsibility to schedule the multiplications.
+- You can use the protocol with other factories to implement other
+ functions that apply to arbitrary lists of longs.
+- You can use the factory with other protocols for support of legacy
+ protocols. In fact, the factory does not even have to be used as
+ a protocol factory. Here are easy ways to support the operation
+ over XML-RPC and PB.
+
+class Multiply(xmlrpc.XMLRPC):
+ def __init__(self): self.factory = Multiplication()
+ def xmlrpc_multiply(self, *numbers):
+ return self.factory.calc(map(long, numbers))
+
+class Multiply(pb.Referencable):
+ def __init__(self): self.factory = Multiplication()
+ def remote_multiply(self, *numbers):
+ return self.factory.calc(map(long, numbers))
+
+Note:
+Multiplying zero numbers is a perfectly sensible operation, and the
+result is 1. In that, this example departs from doc/examples/longex.py,
+which errors out when trying to do this.
+"""
+from __future__ import generators
+from twisted.protocols import basic
+from twisted.internet import defer, protocol
+
+def runIterator(reactor, iterator):
+ try:
+ iterator.next()
+ except StopIteration:
+ pass
+ else:
+ reactor.callLater(0, runIterator, reactor, iterator)
+
+def multiply(numbers):
+ d = defer.Deferred()
+ def _():
+ acc = 1
+ while numbers:
+ acc *= numbers.pop()
+ yield None
+ d.callback(acc)
+ return d, _()
+
+class Numbers(basic.LineReceiver):
+ """Protocol for reading lists of numbers and manipulating them.
+
+ It receives a list of numbers (seperated by whitespace) on a line, and
+ writes back the answer. The exact algorithm to use depends on the
+ factory. It should return an str-able Deferred.
+ """
+ def lineReceived(self, line):
+ try:
+ numbers = map(long, line.split())
+ except ValueError:
+ self.sendLine('Error.')
+ return
+ deferred = self.factory.calc(numbers)
+ deferred.addCallback(str)
+ deferred.addCallback(self.sendLine)
+
+class Multiplication(protocol.ServerFactory):
+ """Factory for multiplying numbers.
+
+ It provides a function which calculates the multiplication
+ of a list of numbers. The function destroys its input.
+ Note that instances of this factory can use other formats
+ for transmitting the number lists, as long as they set
+ correct protoocl values.
+ """
+ protocol = Numbers
+ def calc(self, numbers):
+ deferred, iterator = multiply(numbers)
+ from twisted.internet import reactor
+ runIterator(reactor, iterator)
+ return deferred
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ reactor.listenTCP(1234, Multiplication())
+ reactor.run()
diff --git a/doc/core/examples/mouse.py b/doc/core/examples/mouse.py
new file mode 100755
index 0000000..a62ed81
--- /dev/null
+++ b/doc/core/examples/mouse.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example using MouseMan protocol with the SerialPort transport.
+"""
+
+# TODO set tty modes, etc.
+# This works for me:
+
+# speed 1200 baud; rows 0; columns 0; line = 0;
+# intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D;
+# eol = <undef>; eol2 = <undef>; start = ^Q; stop = ^S; susp = ^Z;
+# rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
+# -parenb -parodd cs7 hupcl -cstopb cread clocal -crtscts ignbrk
+# -brkint ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon
+# -ixoff -iuclc -ixany -imaxbel -opost -olcuc -ocrnl -onlcr -onocr
+# -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 -isig -icanon -iexten
+# -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl
+# -echoke
+
+import sys
+from twisted.python import usage, log
+from twisted.protocols.mice import mouseman
+
+if sys.platform == 'win32':
+ # win32 serial does not work yet!
+ raise NotImplementedError, "The SerialPort transport does not currently support Win32"
+ from twisted.internet import win32eventreactor
+ win32eventreactor.install()
+
+class Options(usage.Options):
+ optParameters = [
+ ['port', 'p', '/dev/mouse', 'Device for serial mouse'],
+ ['baudrate', 'b', '1200', 'Baudrate for serial mouse'],
+ ['outfile', 'o', None, 'Logfile [default: sys.stdout]'],
+ ]
+
+class McFooMouse(mouseman.MouseMan):
+ def down_left(self):
+ log.msg("LEFT")
+
+ def up_left(self):
+ log.msg("left")
+
+ def down_middle(self):
+ log.msg("MIDDLE")
+
+ def up_middle(self):
+ log.msg("middle")
+
+ def down_right(self):
+ log.msg("RIGHT")
+
+ def up_right(self):
+ log.msg("right")
+
+ def move(self, x, y):
+ log.msg("(%d,%d)" % (x, y))
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ from twisted.internet.serialport import SerialPort
+ o = Options()
+ try:
+ o.parseOptions()
+ except usage.UsageError, errortext:
+ print "%s: %s" % (sys.argv[0], errortext)
+ print "%s: Try --help for usage details." % (sys.argv[0])
+ raise SystemExit, 1
+
+ logFile = sys.stdout
+ if o.opts['outfile']:
+ logFile = o.opts['outfile']
+ log.startLogging(logFile)
+
+ SerialPort(McFooMouse(), o.opts['port'], reactor, baudrate=int(o.opts['baudrate']))
+ reactor.run()
diff --git a/doc/core/examples/pb_exceptions.py b/doc/core/examples/pb_exceptions.py
new file mode 100644
index 0000000..00753a4
--- /dev/null
+++ b/doc/core/examples/pb_exceptions.py
@@ -0,0 +1,36 @@
+
+from twisted.python import util
+from twisted.spread import pb
+from twisted.cred import portal, checkers, credentials
+
+class Avatar(pb.Avatar):
+ def perspective_exception(self, x):
+ return x / 0
+
+class Realm:
+ def requestAvatar(self, interface, mind, *interfaces):
+ if pb.IPerspective in interfaces:
+ return pb.IPerspective, Avatar(), lambda: None
+
+def cbLogin(avatar):
+ avatar.callRemote("exception", 10).addCallback(str).addCallback(util.println)
+
+def ebLogin(failure):
+ print failure
+
+def main():
+ c = checkers.InMemoryUsernamePasswordDatabaseDontUse(user="pass")
+ p = portal.Portal(Realm(), [c])
+ server = pb.PBServerFactory(p)
+ server.unsafeTracebacks = True
+ client = pb.PBClientFactory()
+ login = client.login(credentials.UsernamePassword("user", "pass"))
+ login.addCallback(cbLogin).addErrback(ebLogin).addBoth(lambda: reactor.stop())
+
+ from twisted.internet import reactor
+ p = reactor.listenTCP(0, server)
+ c = reactor.connectTCP('127.0.0.1', p.getHost().port, client)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/pbbenchclient.py b/doc/core/examples/pbbenchclient.py
new file mode 100644
index 0000000..9cd2b31
--- /dev/null
+++ b/doc/core/examples/pbbenchclient.py
@@ -0,0 +1,42 @@
+
+from twisted.spread import pb
+from twisted.internet import defer, reactor
+from twisted.cred.credentials import UsernamePassword
+import time
+
+class PBBenchClient:
+ hostname = 'localhost'
+ portno = pb.portno
+ calledThisSecond = 0
+
+ def callLoop(self, ignored):
+ d1 = self.persp.callRemote("simple")
+ d2 = self.persp.callRemote("complexTypes")
+ defer.DeferredList([d1, d2]).addCallback(self.callLoop)
+ self.calledThisSecond += 1
+ thisSecond = int(time.time())
+ if thisSecond != self.lastSecond:
+ if thisSecond - self.lastSecond > 1:
+ print "WARNING it took more than one second"
+ print 'cps:', self.calledThisSecond
+ self.calledThisSecond = 0
+ self.lastSecond = thisSecond
+
+ def _cbPerspective(self, persp):
+ self.persp = persp
+ self.lastSecond = int(time.time())
+ self.callLoop(None)
+
+ def runTest(self):
+ factory = pb.PBClientFactory()
+ reactor.connectTCP(self.hostname, self.portno, factory)
+ factory.login(UsernamePassword("benchmark", "benchmark")).addCallback(self._cbPerspective)
+
+
+def main():
+ PBBenchClient().runTest()
+ from twisted.internet import reactor
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/pbbenchserver.py b/doc/core/examples/pbbenchserver.py
new file mode 100644
index 0000000..324f23f
--- /dev/null
+++ b/doc/core/examples/pbbenchserver.py
@@ -0,0 +1,54 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Server for PB benchmark."""
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred.portal import IRealm
+
+class PBBenchPerspective(pb.Avatar):
+ callsPerSec = 0
+ def __init__(self):
+ pass
+
+ def perspective_simple(self):
+ self.callsPerSec = self.callsPerSec + 1
+ return None
+
+ def printCallsPerSec(self):
+ print '(s) cps:', self.callsPerSec
+ self.callsPerSec = 0
+ reactor.callLater(1, self.printCallsPerSec)
+
+ def perspective_complexTypes(self):
+ return ['a', 1, 1l, 1.0, [], ()]
+
+
+class SimpleRealm:
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective in interfaces:
+ p = PBBenchPerspective()
+ p.printCallsPerSec()
+ return pb.IPerspective, p, lambda : None
+ else:
+ raise NotImplementedError("no interface")
+
+
+def main():
+ from twisted.cred.portal import Portal
+ from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+ portal = Portal(SimpleRealm())
+ checker = InMemoryUsernamePasswordDatabaseDontUse()
+ checker.addUser("benchmark", "benchmark")
+ portal.registerChecker(checker)
+ reactor.listenTCP(8787, pb.PBServerFactory(portal))
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/pbecho.py b/doc/core/examples/pbecho.py
new file mode 100644
index 0000000..45df949
--- /dev/null
+++ b/doc/core/examples/pbecho.py
@@ -0,0 +1,51 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+if __name__ == '__main__':
+ # Avoid using any names defined in the "__main__" module.
+ from pbecho import main
+ raise SystemExit(main())
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.cred.portal import IRealm
+
+class DefinedError(pb.Error):
+ pass
+
+
+class SimplePerspective(pb.Avatar):
+
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+
+ def perspective_error(self):
+ raise DefinedError("exception!")
+
+ def logout(self):
+ print self, "logged out"
+
+
+class SimpleRealm:
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective in interfaces:
+ avatar = SimplePerspective()
+ return pb.IPerspective, avatar, avatar.logout
+ else:
+ raise NotImplementedError("no interface")
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.cred.portal import Portal
+ from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+ portal = Portal(SimpleRealm())
+ checker = InMemoryUsernamePasswordDatabaseDontUse()
+ checker.addUser("guest", "guest")
+ portal.registerChecker(checker)
+ reactor.listenTCP(pb.portno, pb.PBServerFactory(portal))
+ reactor.run()
diff --git a/doc/core/examples/pbechoclient.py b/doc/core/examples/pbechoclient.py
new file mode 100644
index 0000000..b7435f7
--- /dev/null
+++ b/doc/core/examples/pbechoclient.py
@@ -0,0 +1,32 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import reactor
+from twisted.spread import pb
+from twisted.cred.credentials import UsernamePassword
+
+from pbecho import DefinedError
+
+def success(message):
+ print "Message received:",message
+ # reactor.stop()
+
+def failure(error):
+ t = error.trap(DefinedError)
+ print "error received:", t
+ reactor.stop()
+
+def connected(perspective):
+ perspective.callRemote('echo', "hello world").addCallbacks(success, failure)
+ perspective.callRemote('error').addCallbacks(success, failure)
+ print "connected."
+
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("localhost", pb.portno, factory)
+factory.login(
+ UsernamePassword("guest", "guest")).addCallbacks(connected, failure)
+
+reactor.run()
diff --git a/doc/core/examples/pbgtk2.py b/doc/core/examples/pbgtk2.py
new file mode 100644
index 0000000..b5f3e7f
--- /dev/null
+++ b/doc/core/examples/pbgtk2.py
@@ -0,0 +1,122 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from __future__ import nested_scopes
+
+from twisted.internet import gtk2reactor
+gtk2reactor.install()
+
+import gtk
+from gtk import glade
+from twisted import copyright
+from twisted.internet import reactor, defer
+from twisted.python import failure, log, util
+from twisted.spread import pb
+from twisted.cred.credentials import UsernamePassword
+from twisted.internet import error as netError
+
+
+class LoginDialog:
+ def __init__(self, deferred):
+ self.deferredResult = deferred
+
+ gladefile = util.sibpath(__file__, "pbgtk2login.glade")
+ self.glade = glade.XML(gladefile)
+
+ self.glade.signal_autoconnect(self)
+
+ self.setWidgetsFromGladefile()
+ self._loginDialog.show()
+
+ def setWidgetsFromGladefile(self):
+ widgets = ("hostEntry", "portEntry", "userNameEntry", "passwordEntry",
+ "statusBar", "loginDialog")
+ gw = self.glade.get_widget
+ for widgetName in widgets:
+ setattr(self, "_" + widgetName, gw(widgetName))
+
+ self._statusContext = self._statusBar.get_context_id("Login dialog.")
+
+ def on_loginDialog_response(self, widget, response):
+ handlers = {gtk.RESPONSE_NONE: self.windowClosed,
+ gtk.RESPONSE_DELETE_EVENT: self.windowClosed,
+ gtk.RESPONSE_OK: self.doLogin,
+ gtk.RESPONSE_CANCEL: self.cancelled}
+ handlers.get(response)()
+
+ def on_loginDialog_close(self, widget, userdata=None):
+ self.windowClosed()
+
+ def cancelled(self):
+ if not self.deferredResult.called:
+ self.deferredResult.errback()
+ self._loginDialog.destroy()
+
+ def windowClosed(self, reason=None):
+ if not self.deferredResult.called:
+ self.deferredResult.errback()
+
+ def doLogin(self):
+ host = self._hostEntry.get_text()
+ port = int(self._portEntry.get_text())
+ userName = self._userNameEntry.get_text()
+ password = self._passwordEntry.get_text()
+
+ client_factory = pb.PBClientFactory()
+ reactor.connectTCP(host, port, client_factory)
+ creds = UsernamePassword(userName, password)
+ client_factory.login(creds).addCallbacks(self._cbGotPerspective, self._ebFailedLogin)
+
+ self.statusMsg("Contacting server...")
+
+ def _cbGotPerspective(self, perspective):
+ self.statusMsg("Connected to server.")
+ self.deferredResult.callback(perspective)
+ self._loginDialog.destroy()
+
+ def _ebFailedLogin(self, reason):
+ if isinstance(reason, failure.Failure):
+ text = str(reason.value)
+ else:
+ text = str(reason)
+
+ self.statusMsg(text)
+ msg = gtk.MessageDialog(self._loginDialog,
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_ERROR,
+ gtk.BUTTONS_CLOSE,
+ text)
+ msg.show_all()
+ msg.connect("response", lambda *a: msg.destroy())
+
+ def statusMsg(self, text):
+ self._statusBar.push(self._statusContext, text)
+
+
+class EchoClient:
+ def __init__(self, echoer):
+ self.echoer = echoer
+ w = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.VBox(); b = gtk.Button("Echo:")
+ self.entry = gtk.Entry(); self.outry = gtk.Entry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', self.stop)
+ w.show_all()
+
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.callRemote('echo',txt).addCallback(self.outry.set_text)
+
+ def stop(self, b):
+ reactor.stop()
+
+d = defer.Deferred()
+LoginDialog(d)
+d.addCallbacks(EchoClient,
+ lambda _: reactor.stop())
+
+reactor.run()
diff --git a/doc/core/examples/pbgtk2login.glade b/doc/core/examples/pbgtk2login.glade
new file mode 100644
index 0000000..6b5eb01
--- /dev/null
+++ b/doc/core/examples/pbgtk2login.glade
@@ -0,0 +1,330 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="loginDialog">
+ <property name="title" translatable="yes">Login</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="has_separator">True</property>
+ <signal name="response" handler="on_loginDialog_response" last_modification_time="Sun, 21 Sep 2003 05:27:45 GMT"/>
+ <signal name="close" handler="on_loginDialog_close" last_modification_time="Sun, 21 Sep 2003 05:27:49 GMT"/>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="cancelbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="loginButton">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="response_id">-5</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-ok</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Login</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkStatusbar" id="statusBar">
+ <property name="visible">True</property>
+ <property name="has_resize_grip">False</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="hostLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Host:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.9</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">hostEntry</property>
+ <accessibility>
+ <atkrelation target="hostEntry" type="label-for"/>
+ <atkrelation target="portEntry" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkEntry" id="hostEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">The name of a host to connect to.</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">localhost</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ <accessibility>
+ <atkrelation target="hostLabel" type="labelled-by"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="portEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">The number of a port to connect on.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">8787</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">5</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="nameLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Name:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.9</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">userNameEntry</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="userNameEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">An identity to log in as.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">guest</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="passwordEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">The Identity's log-in password.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">False</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">guest</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="passwordLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Password:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.9</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">passwordEntry</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/doc/core/examples/pbinterop.py b/doc/core/examples/pbinterop.py
new file mode 100644
index 0000000..167121e
--- /dev/null
+++ b/doc/core/examples/pbinterop.py
@@ -0,0 +1,71 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""PB interop server."""
+
+from twisted.spread import pb, jelly, flavors
+from twisted.internet import reactor
+
+
+class Interop(pb.Root):
+ """Test object for PB interop tests."""
+
+ def __init__(self):
+ self.o = pb.Referenceable()
+
+ def remote_int(self):
+ return 1
+
+ def remote_string(self):
+ return "string"
+
+ def remote_unicode(self):
+ return u"string"
+
+ def remote_float(self):
+ return 1.5
+
+ def remote_list(self):
+ return [1, 2, 3]
+
+ def remote_recursive(self):
+ l = []
+ l.append(l)
+ return l
+
+ def remote_dict(self):
+ return {1 : 2}
+
+ def remote_reference(self):
+ return self.o
+
+ def remote_local(self, obj):
+ d = obj.callRemote("hello")
+ d.addCallback(self._local_success)
+
+ def _local_success(self, result):
+ if result != "hello, world":
+ raise ValueError, "%r != %r" % (result, "hello, world")
+
+ def remote_receive(self, obj):
+ expected = [1, 1.5, "hi", u"hi", {1 : 2}]
+ if obj != expected:
+ raise ValueError, "%r != %r" % (obj, expected)
+
+ def remote_self(self, obj):
+ if obj != self:
+ raise ValueError, "%r != %r" % (obj, self)
+
+ def remote_copy(self, x):
+ o = flavors.Copyable()
+ o.x = x
+ return o
+
+
+if __name__ == '__main__':
+ reactor.listenTCP(8789, pb.PBServerFactory(Interop()))
+ reactor.run()
+
+
+
diff --git a/doc/core/examples/pbsimple.py b/doc/core/examples/pbsimple.py
new file mode 100644
index 0000000..68e4cbf
--- /dev/null
+++ b/doc/core/examples/pbsimple.py
@@ -0,0 +1,16 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Echoer(pb.Root):
+ def remote_echo(self, st):
+ print 'echoing:', st
+ return st
+
+if __name__ == '__main__':
+ reactor.listenTCP(8789, pb.PBServerFactory(Echoer()))
+ reactor.run()
diff --git a/doc/core/examples/pbsimpleclient.py b/doc/core/examples/pbsimpleclient.py
new file mode 100644
index 0000000..05cb50a
--- /dev/null
+++ b/doc/core/examples/pbsimpleclient.py
@@ -0,0 +1,18 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.python import util
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("localhost", 8789, factory)
+d = factory.getRootObject()
+d.addCallback(lambda object: object.callRemote("echo", "hello network"))
+d.addCallback(lambda echo: 'server echoed: '+echo)
+d.addErrback(lambda reason: 'error: '+str(reason.value))
+d.addCallback(util.println)
+d.addCallback(lambda _: reactor.stop())
+reactor.run()
diff --git a/doc/core/examples/postfix.py b/doc/core/examples/postfix.py
new file mode 100644
index 0000000..c13d640
--- /dev/null
+++ b/doc/core/examples/postfix.py
@@ -0,0 +1,29 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test app for PostfixTCPMapServer.
+
+Call with parameters KEY1=VAL1 KEY2=VAL2 ...
+"""
+
+import sys
+
+from twisted.internet import reactor
+from twisted.protocols import postfix
+from twisted.python import log
+
+log.startLogging(sys.stdout)
+
+d = {}
+for arg in sys.argv[1:]:
+ try:
+ k,v = arg.split('=', 1)
+ except ValueError:
+ k = arg
+ v = ''
+ d[k] = v
+
+f = postfix.PostfixTCPMapDictServerFactory(d)
+port = reactor.listenTCP(4242, f, interface='127.0.0.1')
+reactor.run()
diff --git a/doc/core/examples/ptyserv.py b/doc/core/examples/ptyserv.py
new file mode 100644
index 0000000..4e736a9
--- /dev/null
+++ b/doc/core/examples/ptyserv.py
@@ -0,0 +1,45 @@
+# Copyright (c) Twisted Matrix Laboratories
+# See LICENSE for details
+
+"""
+A PTY server that spawns a shell upon connection.
+
+Run this example by typing in:
+> python ptyserv.py
+
+Telnet to the server once you start it by typing in:
+> telnet localhost 5823
+"""
+
+from twisted.internet import reactor, protocol
+
+class FakeTelnet(protocol.Protocol):
+ commandToRun = ['/bin/sh'] # could have args too
+ dirToRunIn = '/tmp'
+ def connectionMade(self):
+ print 'connection made'
+ self.propro = ProcessProtocol(self)
+ reactor.spawnProcess(self.propro, self.commandToRun[0], self.commandToRun, {},
+ self.dirToRunIn, usePTY=1)
+ def dataReceived(self, data):
+ self.propro.transport.write(data)
+ def conectionLost(self):
+ print 'connection lost'
+ self.propro.tranport.loseConnection()
+
+class ProcessProtocol(protocol.ProcessProtocol):
+
+ def __init__(self, pr):
+ self.pr = pr
+
+ def outReceived(self, data):
+ self.pr.transport.write(data)
+
+ def processEnded(self, reason):
+ print 'protocol conection lost'
+ self.pr.transport.loseConnection()
+
+f = protocol.Factory()
+f.protocol = FakeTelnet
+reactor.listenTCP(5823, f)
+reactor.run()
diff --git a/doc/core/examples/pyui_bg.png b/doc/core/examples/pyui_bg.png
new file mode 100644
index 0000000..08d45ec
--- /dev/null
+++ b/doc/core/examples/pyui_bg.png
Binary files differ
diff --git a/doc/core/examples/pyuidemo.py b/doc/core/examples/pyuidemo.py
new file mode 100755
index 0000000..3b7a835
--- /dev/null
+++ b/doc/core/examples/pyuidemo.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Displays a frame with two buttons and a background image, using pyui library.
+
+Run this example by typing in:
+ python pyuidemo.py
+
+Select "Quit" button to exit demo.
+"""
+
+import pyui
+from twisted.internet import reactor, pyuisupport
+
+def onButton(self):
+ print "got a button"
+
+def onQuit(self):
+ reactor.stop()
+
+def main():
+ pyuisupport.install(args=(640, 480), kw={'renderer': '2d'})
+
+ w = pyui.widgets.Frame(50, 50, 400, 400, "clipme")
+ b = pyui.widgets.Button("A button is here", onButton)
+ q = pyui.widgets.Button("Quit!", onQuit)
+
+ w.addChild(b)
+ w.addChild(q)
+ w.pack()
+
+ w.setBackImage("pyui_bg.png")
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/recvfd.py b/doc/core/examples/recvfd.py
new file mode 100644
index 0000000..6d17c5f
--- /dev/null
+++ b/doc/core/examples/recvfd.py
@@ -0,0 +1,90 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Client-side of an example for sending file descriptors between processes over
+UNIX sockets. This client connects to a server listening on a UNIX socket and
+waits for one file descriptor to arrive over the connection. It displays the
+name of the file and the first 80 bytes it contains, then exits.
+
+To runb this example, run this program with one argument: a path giving the UNIX
+socket the server side of this example is already listening on. For example:
+
+ $ python recvfd.py /tmp/sendfd.sock
+
+See sendfd.py for the server side of this example.
+"""
+
+if __name__ == '__main__':
+ import recvfd
+ raise SystemExit(recvfd.main())
+
+import os, sys
+
+from zope.interface import implements
+
+from twisted.python.log import startLogging
+from twisted.python.filepath import FilePath
+from twisted.internet.defer import Deferred
+from twisted.internet.interfaces import IFileDescriptorReceiver
+from twisted.internet.protocol import Factory
+from twisted.protocols.basic import LineOnlyReceiver
+from twisted.internet.endpoints import UNIXClientEndpoint
+from twisted.internet import reactor
+
+class ReceiveFDProtocol(LineOnlyReceiver):
+ implements(IFileDescriptorReceiver)
+
+ descriptor = None
+
+ def __init__(self):
+ self.whenDisconnected = Deferred()
+
+
+ def fileDescriptorReceived(self, descriptor):
+ # Record the descriptor sent to us
+ self.descriptor = descriptor
+
+
+ def lineReceived(self, line):
+ if self.descriptor is None:
+ print "Received %r without receiving descriptor!" % (line,)
+ else:
+ # Use the previously received descriptor, along with the newly
+ # provided information about which file it is, to present some
+ # information to the user.
+ data = os.read(self.descriptor, 80)
+ print "Received %r from the server." % (line,)
+ print "First 80 bytes are:\n%r\n" % (data,)
+ os.close(self.descriptor)
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ self.whenDisconnected.callback(None)
+
+
+
+def main():
+ address = FilePath(sys.argv[1])
+
+ startLogging(sys.stdout)
+
+ factory = Factory()
+ factory.protocol = ReceiveFDProtocol
+ factory.quiet = True
+
+ endpoint = UNIXClientEndpoint(reactor, address.path)
+ connected = endpoint.connect(factory)
+
+ def succeeded(client):
+ return client.whenDisconnected
+ def failed(reason):
+ print "Could not connect:", reason.getErrorMessage()
+ def disconnected(ignored):
+ reactor.stop()
+
+ connected.addCallbacks(succeeded, failed)
+ connected.addCallback(disconnected)
+
+ reactor.run()
diff --git a/doc/core/examples/rotatinglog.py b/doc/core/examples/rotatinglog.py
new file mode 100644
index 0000000..288753c
--- /dev/null
+++ b/doc/core/examples/rotatinglog.py
@@ -0,0 +1,26 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of using the rotating log.
+"""
+
+from twisted.python import log
+from twisted.python import logfile
+
+# rotate every 100 bytes
+f = logfile.LogFile("test.log", "/tmp", rotateLength=100)
+
+# setup logging to use our new logfile
+log.startLogging(f)
+
+# print a few message
+for i in range(10):
+ log.msg("this is a test of the logfile: %s" % i)
+
+# rotate the logfile manually
+f.rotate()
+
+log.msg("goodbye")
diff --git a/doc/core/examples/sendfd.py b/doc/core/examples/sendfd.py
new file mode 100644
index 0000000..b1948aa
--- /dev/null
+++ b/doc/core/examples/sendfd.py
@@ -0,0 +1,83 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Server-side of an example for sending file descriptors between processes over
+UNIX sockets. This server accepts connections on a UNIX socket and sends one
+file descriptor to them, along with the name of the file it is associated with.
+
+To run this example, run this program with two arguments: a path giving a UNIX
+socket to listen on (must not exist) and a path to a file to send to clients
+which connect (must exist). For example:
+
+ $ python sendfd.py /tmp/sendfd.sock /etc/motd
+
+It will listen for client connections until stopped (eg, using Control-C). Most
+interesting behavior happens on the client side.
+
+See recvfd.py for the client side of this example.
+"""
+
+if __name__ == '__main__':
+ import sendfd
+ raise SystemExit(sendfd.main())
+
+import sys
+
+from twisted.python.log import startLogging
+from twisted.python.filepath import FilePath
+from twisted.internet.protocol import Factory
+from twisted.protocols.basic import LineOnlyReceiver
+from twisted.internet import reactor
+
+class SendFDProtocol(LineOnlyReceiver):
+ def connectionMade(self):
+ # Open the desired file and keep a reference to it - keeping it open
+ # until we know the other side has it. Closing it early will prevent
+ # it from actually being sent.
+ self.fObj = self.factory.content.open()
+
+ # Tell the transport to send it. It is not necessarily sent when this
+ # method returns. The reactor may need to run for a while longer before
+ # that happens.
+ self.transport.sendFileDescriptor(self.fObj.fileno())
+
+ # Send along *at least* one byte, since one file descriptor was sent.
+ # In this case, send along the name of the file to let the other side
+ # have some idea what they're getting.
+ self.sendLine(self.factory.content.path)
+
+ # Give the other side a minute to deal with this. If they don't close
+ # the connection by then, we will do it for them.
+ self.timeoutCall = reactor.callLater(60, self.transport.loseConnection)
+
+
+ def connectionLost(self, reason):
+ # Clean up the file object, it is no longer needed.
+ self.fObj.close()
+ self.fObj = None
+
+ # Clean up the timeout, if necessary.
+ if self.timeoutCall.active():
+ self.timeoutCall.cancel()
+ self.timeoutCall = None
+
+
+def main():
+ address = FilePath(sys.argv[1])
+ content = FilePath(sys.argv[2])
+
+ if address.exists():
+ raise SystemExit("Cannot listen on an existing path")
+
+ if not content.isfile():
+ raise SystemExit("Content file must exist")
+
+ startLogging(sys.stdout)
+
+ serverFactory = Factory()
+ serverFactory.content = content
+ serverFactory.protocol = SendFDProtocol
+
+ port = reactor.listenUNIX(address.path, serverFactory)
+ reactor.run()
diff --git a/doc/core/examples/server.pem b/doc/core/examples/server.pem
new file mode 100644
index 0000000..80ef9dc
--- /dev/null
+++ b/doc/core/examples/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv
+c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB
+BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC
+MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
+MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7
+hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT
+CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw
+dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
+LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6
+BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++
+7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE
+WUQ9Ho4EzbYCOQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx
+OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT
+ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4
+nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2
+HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA
+oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBDTCBuAIBADBTMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQ
+BgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20w
+XDANBgkqhkiG9w0BAQEFAANLADBIAkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5Ad
+NgLzJ1/MfsQQJ7hHVeHmTAjM664V+fXvwUGJLziCeBo1ysWLRnl8CQIDAQABoAAw
+DQYJKoZIhvcNAQEEBQADQQA7uqbrNTjVWpF6By5ZNPvhZ4YdFgkeXFVWi5ao/TaP
+Vq4BG021fJ9nlHRtr4rotpgHDX1rr+iWeHKsx4+5DRSy
+-----END CERTIFICATE REQUEST-----
diff --git a/doc/core/examples/shaper.py b/doc/core/examples/shaper.py
new file mode 100644
index 0000000..573d67c
--- /dev/null
+++ b/doc/core/examples/shaper.py
@@ -0,0 +1,52 @@
+# -*- Python -*-
+
+"""Example of rate-limiting your web server.
+
+Caveat emptor: While the transfer rates imposed by this mechanism will
+look accurate with wget's rate-meter, don't forget to examine your network
+interface's traffic statistics as well. The current implementation tends
+to create lots of small packets in some conditions, and each packet carries
+with it some bytes of overhead. Check to make sure this overhead is not
+costing you more bandwidth than you are saving by limiting the rate!
+"""
+
+from twisted.protocols import htb
+# for picklability
+import shaper
+
+serverFilter = htb.HierarchicalBucketFilter()
+serverBucket = htb.Bucket()
+
+# Cap total server traffic at 20 kB/s
+serverBucket.maxburst = 20000
+serverBucket.rate = 20000
+
+serverFilter.buckets[None] = serverBucket
+
+# Web service is also limited per-host:
+class WebClientBucket(htb.Bucket):
+ # Your first 10k is free
+ maxburst = 10000
+ # One kB/s thereafter.
+ rate = 1000
+
+webFilter = htb.FilterByHost(serverFilter)
+webFilter.bucketFactory = shaper.WebClientBucket
+
+servertype = "web" # "chargen"
+
+if servertype == "web":
+ from twisted.web import server, static
+ site = server.Site(static.File("/var/www"))
+ site.protocol = htb.ShapedProtocolFactory(site.protocol, webFilter)
+elif servertype == "chargen":
+ from twisted.protocols import wire
+ from twisted.internet import protocol
+
+ site = protocol.ServerFactory()
+ site.protocol = htb.ShapedProtocolFactory(wire.Chargen, webFilter)
+ #site.protocol = wire.Chargen
+
+from twisted.internet import reactor
+reactor.listenTCP(8000, site)
+reactor.run()
diff --git a/doc/core/examples/shoutcast.py b/doc/core/examples/shoutcast.py
new file mode 100644
index 0000000..26c7a7d
--- /dev/null
+++ b/doc/core/examples/shoutcast.py
@@ -0,0 +1,26 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example Shoutcast client. Run with:
+
+python shoutcast.py localhost 8080
+"""
+
+import sys
+
+from twisted.internet import protocol, reactor
+from twisted.protocols.shoutcast import ShoutcastClient
+
+class Test(ShoutcastClient):
+ def gotMetaData(self, data):
+ print "meta:", data
+
+ def gotMP3Data(self, data):
+ pass
+
+host = sys.argv[1]
+port = int(sys.argv[2])
+
+protocol.ClientCreator(reactor, Test).connectTCP(host, port)
+reactor.run()
diff --git a/doc/core/examples/simple.tac b/doc/core/examples/simple.tac
new file mode 100644
index 0000000..02b3f81
--- /dev/null
+++ b/doc/core/examples/simple.tac
@@ -0,0 +1,39 @@
+# You can run this .tac file directly with:
+# twistd -ny simple.tac
+
+from twisted.application import service, internet
+from twisted.protocols import wire
+from twisted.internet import protocol
+from twisted.python import util
+
+application = service.Application('test')
+s = service.IServiceCollection(application)
+factory = protocol.ServerFactory()
+factory.protocol = wire.Echo
+internet.TCPServer(8080, factory).setServiceParent(s)
+
+internet.TCPServer(8081, factory).setServiceParent(s)
+internet.TimerService(5, util.println, "--MARK--").setServiceParent(s)
+
+class Foo(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.write('lalala\n')
+ def dataReceived(self, data):
+ print `data`
+
+factory = protocol.ClientFactory()
+factory.protocol = Foo
+internet.TCPClient('localhost', 8081, factory).setServiceParent(s)
+
+class FooService(service.Service):
+ def startService(self):
+ service.Service.startService(self)
+ print 'lala, starting'
+ def stopService(self):
+ service.Service.stopService(self)
+ print 'lala, stopping'
+ print self.parent.getServiceNamed(self.name) is self
+
+foo = FooService()
+foo.setName('foo')
+foo.setServiceParent(s)
diff --git a/doc/core/examples/simpleclient.py b/doc/core/examples/simpleclient.py
new file mode 100644
index 0000000..bba9f64
--- /dev/null
+++ b/doc/core/examples/simpleclient.py
@@ -0,0 +1,49 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example client. Run simpleserv.py first before running this.
+"""
+
+from twisted.internet import reactor, protocol
+
+
+# a client protocol
+
+class EchoClient(protocol.Protocol):
+ """Once connected, send a message, then print the result."""
+
+ def connectionMade(self):
+ self.transport.write("hello, world!")
+
+ def dataReceived(self, data):
+ "As soon as any data is received, write it back."
+ print "Server said:", data
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ print "connection lost"
+
+class EchoFactory(protocol.ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ print "Connection failed - goodbye!"
+ reactor.stop()
+
+ def clientConnectionLost(self, connector, reason):
+ print "Connection lost - goodbye!"
+ reactor.stop()
+
+
+# this connects the protocol to a server runing on port 8000
+def main():
+ f = EchoFactory()
+ reactor.connectTCP("localhost", 8000, f)
+ reactor.run()
+
+# this only runs if the module was *not* imported
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/simpleserv.py b/doc/core/examples/simpleserv.py
new file mode 100644
index 0000000..228fe44
--- /dev/null
+++ b/doc/core/examples/simpleserv.py
@@ -0,0 +1,26 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import reactor, protocol
+
+
+class Echo(protocol.Protocol):
+ """This is just about the simplest possible protocol"""
+
+ def dataReceived(self, data):
+ "As soon as any data is received, write it back."
+ self.transport.write(data)
+
+
+def main():
+ """This runs the protocol on port 8000"""
+ factory = protocol.ServerFactory()
+ factory.protocol = Echo
+ reactor.listenTCP(8000,factory)
+ reactor.run()
+
+# this only runs if the module was *not* imported
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/stdin.py b/doc/core/examples/stdin.py
new file mode 100644
index 0000000..e987be5
--- /dev/null
+++ b/doc/core/examples/stdin.py
@@ -0,0 +1,30 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of reading a line at a time from standard input
+without blocking the reactor.
+"""
+
+from twisted.internet import stdio
+from twisted.protocols import basic
+
+class Echo(basic.LineReceiver):
+ from os import linesep as delimiter
+
+ def connectionMade(self):
+ self.transport.write('>>> ')
+
+ def lineReceived(self, line):
+ self.sendLine('Echo: ' + line)
+ self.transport.write('>>> ')
+
+def main():
+ stdio.StandardIO(Echo())
+ from twisted.internet import reactor
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/stdiodemo.py b/doc/core/examples/stdiodemo.py
new file mode 100644
index 0000000..9132428
--- /dev/null
+++ b/doc/core/examples/stdiodemo.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example using stdio, Deferreds, LineReceiver and twisted.web.client.
+
+Note that the WebCheckerCommandProtocol protocol could easily be used in e.g.
+a telnet server instead; see the comments for details.
+
+Based on an example by Abe Fettig.
+"""
+
+from twisted.internet import stdio, reactor
+from twisted.protocols import basic
+from twisted.web import client
+
+class WebCheckerCommandProtocol(basic.LineReceiver):
+ delimiter = '\n' # unix terminal style newlines. remove this line
+ # for use with Telnet
+
+ def connectionMade(self):
+ self.sendLine("Web checker console. Type 'help' for help.")
+
+ def lineReceived(self, line):
+ # Ignore blank lines
+ if not line: return
+
+ # Parse the command
+ commandParts = line.split()
+ command = commandParts[0].lower()
+ args = commandParts[1:]
+
+ # Dispatch the command to the appropriate method. Note that all you
+ # need to do to implement a new command is add another do_* method.
+ try:
+ method = getattr(self, 'do_' + command)
+ except AttributeError, e:
+ self.sendLine('Error: no such command.')
+ else:
+ try:
+ method(*args)
+ except Exception, e:
+ self.sendLine('Error: ' + str(e))
+
+ def do_help(self, command=None):
+ """help [command]: List commands, or show help on the given command"""
+ if command:
+ self.sendLine(getattr(self, 'do_' + command).__doc__)
+ else:
+ commands = [cmd[3:] for cmd in dir(self) if cmd.startswith('do_')]
+ self.sendLine("Valid commands: " +" ".join(commands))
+
+ def do_quit(self):
+ """quit: Quit this session"""
+ self.sendLine('Goodbye.')
+ self.transport.loseConnection()
+
+ def do_check(self, url):
+ """check <url>: Attempt to download the given web page"""
+ client.getPage(url).addCallback(
+ self.__checkSuccess).addErrback(
+ self.__checkFailure)
+
+ def __checkSuccess(self, pageData):
+ self.sendLine("Success: got %i bytes." % len(pageData))
+
+ def __checkFailure(self, failure):
+ self.sendLine("Failure: " + failure.getErrorMessage())
+
+ def connectionLost(self, reason):
+ # stop the reactor, only because this is meant to be run in Stdio.
+ reactor.stop()
+
+if __name__ == "__main__":
+ stdio.StandardIO(WebCheckerCommandProtocol())
+ reactor.run()
diff --git a/doc/core/examples/streaming.py b/doc/core/examples/streaming.py
new file mode 100644
index 0000000..06fcfd7
--- /dev/null
+++ b/doc/core/examples/streaming.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This is a sample implementation of a Twisted push producer/consumer system. It
+consists of a TCP server which asks the user how many random integers they
+want, and it sends the result set back to the user, one result per line,
+and finally closes the connection.
+"""
+
+from sys import stdout
+from random import randrange
+
+from zope.interface import implements
+from twisted.python.log import startLogging
+from twisted.internet import interfaces, reactor
+from twisted.internet.protocol import Factory
+from twisted.protocols.basic import LineReceiver
+
+
+class Producer(object):
+ """
+ Send back the requested number of random integers to the client.
+ """
+
+ implements(interfaces.IPushProducer)
+
+ def __init__(self, proto, count):
+ self._proto = proto
+ self._goal = count
+ self._produced = 0
+ self._paused = False
+
+ def pauseProducing(self):
+ """
+ When we've produced data too fast, pauseProducing() will be called
+ (reentrantly from within resumeProducing's sendLine() method, most
+ likely), so set a flag that causes production to pause temporarily.
+ """
+ self._paused = True
+ print 'Pausing connection from %s' % self._proto.transport.getPeer()
+
+ def resumeProducing(self):
+ """
+ Resume producing integers.
+
+ This tells the push producer to (re-)add itself to the main loop and
+ produce integers for its consumer until the requested number of integers
+ were returned to the client.
+ """
+ self._paused = False
+
+ while not self._paused and self._produced < self._goal:
+ next_int = randrange(0, 10000)
+ self._proto.sendLine('%d' % next_int)
+ self._produced += 1
+
+ if self._produced == self._goal:
+ self._proto.transport.unregisterProducer()
+ self._proto.transport.loseConnection()
+
+ def stopProducing(self):
+ """
+ When a consumer has died, stop producing data for good.
+ """
+ self._produced = self._goal
+
+
+class ServeRandom(LineReceiver):
+ """
+ Serve up random integers.
+ """
+
+ def connectionMade(self):
+ """
+ Once the connection is made we ask the client how many random integers
+ the producer should return.
+ """
+ print 'Connection made from %s' % self.transport.getPeer()
+ self.sendLine('How many random integers do you want?')
+
+ def lineReceived(self, line):
+ """
+ This checks how many random integers the client expects in return and
+ tells the producer to start generating the data.
+ """
+ count = int(line.strip())
+ print 'Client requested %d random integers!' % count
+ producer = Producer(self, count)
+ self.transport.registerProducer(producer, True)
+ producer.resumeProducing()
+
+ def connectionLost(self, reason):
+ print 'Connection lost from %s' % self.transport.getPeer()
+
+
+startLogging(stdout)
+factory = Factory()
+factory.protocol = ServeRandom
+reactor.listenTCP(1234, factory)
+reactor.run()
diff --git a/doc/core/examples/testlogging.py b/doc/core/examples/testlogging.py
new file mode 100644
index 0000000..d44def7
--- /dev/null
+++ b/doc/core/examples/testlogging.py
@@ -0,0 +1,41 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Test logging.
+
+Message should only be printed second time around.
+"""
+
+from twisted.python import log
+from twisted.internet import reactor
+
+import sys, warnings
+
+def test(i):
+ print "printed", i
+ log.msg("message %s" % i)
+ warnings.warn("warning %s" % i)
+ try:
+ raise RuntimeError, "error %s" % i
+ except:
+ log.err()
+
+def startlog():
+ log.startLogging(sys.stdout)
+
+def end():
+ reactor.stop()
+
+# pre-reactor run
+test(1)
+
+# after reactor run
+reactor.callLater(0.1, test, 2)
+reactor.callLater(0.2, startlog)
+
+# after startLogging
+reactor.callLater(0.3, test, 3)
+reactor.callLater(0.4, end)
+
+reactor.run()
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib
new file mode 100644
index 0000000..71cb459
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib
@@ -0,0 +1,13 @@
+{
+ IBClasses = (
+ {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; },
+ {
+ ACTIONS = {doTwistzillaFetch = id; };
+ CLASS = MyAppDelegate;
+ LANGUAGE = ObjC;
+ OUTLETS = {messageTextField = id; progressIndicator = id; resultTextField = id; };
+ SUPERCLASS = NSObject;
+ }
+ );
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib
new file mode 100644
index 0000000..4f99a2d
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBEditorPositions</key>
+ <dict>
+ <key>29</key>
+ <string>127 344 318 44 0 0 1600 1002 </string>
+ </dict>
+ <key>IBFramework Version</key>
+ <string>291.0</string>
+ <key>IBLockedObjects</key>
+ <array>
+ <integer>204</integer>
+ </array>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>21</integer>
+ <integer>29</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>6L60</string>
+</dict>
+</plist>
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nib b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 0000000..e5caaf0
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt
new file mode 100644
index 0000000..96010e2
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt
@@ -0,0 +1,6 @@
+Requires PyObjC 1.3.1 (svn r1589 or later)
+
+To run the demo:
+
+python setup.py py2app
+open dist/Twistzilla.app
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py
new file mode 100644
index 0000000..45eeb8d
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py
@@ -0,0 +1,79 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+# import needed classes/functions from Cocoa
+from Foundation import *
+from AppKit import *
+
+# import Nib loading functionality from AppKit
+from PyObjCTools import NibClassBuilder, AppHelper
+
+from twisted.internet import _threadedselect
+_threadedselect.install()
+
+from twisted.internet import reactor, protocol
+from twisted.web import http
+from twisted.python import log
+import sys, urlparse
+
+# create ObjC classes as defined in MainMenu.nib
+NibClassBuilder.extractClasses("MainMenu")
+class TwistzillaClient(http.HTTPClient):
+ def __init__(self, delegate, urls):
+ self.urls = urls
+ self.delegate = delegate
+
+ def connectionMade(self):
+ self.sendCommand('GET', str(self.urls[2]))
+ self.sendHeader('Host', '%s:%d' % (self.urls[0], self.urls[1]))
+ self.sendHeader('User-Agent', 'CocoaTwistzilla')
+ self.endHeaders()
+
+ def handleResponse(self, data):
+ self.delegate.gotResponse_(data)
+
+class MyAppDelegate(NibClassBuilder.AutoBaseClass):
+ def gotResponse_(self, html):
+ s = self.resultTextField.textStorage()
+ s.replaceCharactersInRange_withString_((0, s.length()), html)
+ self.progressIndicator.stopAnimation_(self)
+
+ def doTwistzillaFetch_(self, sender):
+ s = self.resultTextField.textStorage()
+ s.deleteCharactersInRange_((0, s.length()))
+ self.progressIndicator.startAnimation_(self)
+ u = urlparse.urlparse(self.messageTextField.stringValue())
+ pos = u[1].find(':')
+ if pos == -1:
+ host, port = u[1], 80
+ else:
+ host, port = u[1][:pos], int(u[1][pos+1:])
+ if u[2] == '':
+ fname = '/'
+ else:
+ fname = u[2]
+ host = host.encode('utf8')
+ fname = fname.encode('utf8')
+ protocol.ClientCreator(reactor, TwistzillaClient, self, (host, port, fname)).connectTCP(host, port).addErrback(lambda f:self.gotResponse_(f.getBriefTraceback()))
+
+ def applicationDidFinishLaunching_(self, aNotification):
+ """
+ Invoked by NSApplication once the app is done launching and
+ immediately before the first pass through the main event
+ loop.
+ """
+ self.messageTextField.setStringValue_("http://www.twistedmatrix.com/")
+ reactor.interleave(AppHelper.callAfter)
+
+ def applicationShouldTerminate_(self, sender):
+ if reactor.running:
+ reactor.addSystemEventTrigger(
+ 'after', 'shutdown', AppHelper.stopEventLoop)
+ reactor.stop()
+ return False
+ return True
+
+if __name__ == '__main__':
+ log.startLogging(sys.stdout)
+ AppHelper.runEventLoop()
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py
new file mode 100644
index 0000000..f3afe8a
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py
@@ -0,0 +1,14 @@
+"""
+Script for building the example.
+
+Usage:
+ python setup.py py2app
+"""
+
+from distutils.core import setup
+import py2app
+
+setup(
+ app = ['Twistzilla.py'],
+ data_files = ["English.lproj"],
+)
diff --git a/doc/core/examples/threadedselect/README b/doc/core/examples/threadedselect/README
new file mode 100644
index 0000000..5d3feab
--- /dev/null
+++ b/doc/core/examples/threadedselect/README
@@ -0,0 +1,15 @@
+The examples in this directory import a private module from the
+twisted.internet package. The _threadedselect module provides an object
+which is similar to a Twisted reactor in many ways, but which is not
+actually intended to be used in the same way as a Twisted reactor (it has a
+method named interleave which is intended to be the main entrypoint). This
+functionality should be considered highly experimental and the API subject
+to change at any time.
+
+Possibly the best way to make use of this functionality is to use it to
+implement an object which actually presents the Twisted reactor interface
+(specifically, an object with a run method). That object can then be used
+by application-code in the usual way.
+
+Another course of action is to avoid _threadedselect entirely until the
+issues surrounding it have been resolved.
diff --git a/doc/core/examples/threadedselect/blockingdemo.py b/doc/core/examples/threadedselect/blockingdemo.py
new file mode 100644
index 0000000..7dc98df
--- /dev/null
+++ b/doc/core/examples/threadedselect/blockingdemo.py
@@ -0,0 +1,92 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import _threadedselect
+_threadedselect.install()
+
+from twisted.internet.defer import Deferred
+from twisted.python.failure import Failure
+from twisted.internet import reactor
+from twisted.python.runtime import seconds
+from itertools import count
+from Queue import Queue, Empty
+
+class TwistedManager(object):
+ def __init__(self):
+ self.twistedQueue = Queue()
+ self.key = count()
+ self.results = {}
+
+ def getKey(self):
+ # get a unique identifier
+ return self.key.next()
+
+ def start(self):
+ # start the reactor
+ reactor.interleave(self.twistedQueue.put)
+
+ def _stopIterating(self, value, key):
+ self.results[key] = value
+
+ def stop(self):
+ # stop the reactor
+ key = self.getKey()
+ reactor.addSystemEventTrigger('after', 'shutdown',
+ self._stopIterating, True, key)
+ reactor.stop()
+ self.iterate(key)
+
+ def getDeferred(self, d):
+ # get the result of a deferred or raise if it failed
+ key = self.getKey()
+ d.addBoth(self._stopIterating, key)
+ res = self.iterate(key)
+ if isinstance(res, Failure):
+ res.raiseException()
+ return res
+
+ def poll(self, noLongerThan=1.0):
+ # poll the reactor for up to noLongerThan seconds
+ base = seconds()
+ try:
+ while (seconds() - base) <= noLongerThan:
+ callback = self.twistedQueue.get_nowait()
+ callback()
+ except Empty:
+ pass
+
+ def iterate(self, key=None):
+ # iterate the reactor until it has the result we're looking for
+ while key not in self.results:
+ callback = self.twistedQueue.get()
+ callback()
+ return self.results.pop(key)
+
+def fakeDeferred(msg):
+ d = Deferred()
+ def cb():
+ print "deferred called back"
+ d.callback(msg)
+ reactor.callLater(2, cb)
+ return d
+
+def fakeCallback():
+ print "twisted is still running"
+
+def main():
+ m = TwistedManager()
+ print "starting"
+ m.start()
+ print "setting up a 1sec callback"
+ reactor.callLater(1, fakeCallback)
+ print "getting a deferred"
+ res = m.getDeferred(fakeDeferred("got it!"))
+ print "got the deferred:", res
+ print "stopping"
+ m.stop()
+ print "stopped"
+
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/threadedselect/pygamedemo.py b/doc/core/examples/threadedselect/pygamedemo.py
new file mode 100644
index 0000000..a2bec33
--- /dev/null
+++ b/doc/core/examples/threadedselect/pygamedemo.py
@@ -0,0 +1,78 @@
+from __future__ import generators
+
+# import Twisted and install
+from twisted.internet import _threadedselect
+_threadedselect.install()
+from twisted.internet import reactor
+
+import os
+
+import pygame
+from pygame.locals import *
+
+try:
+ import pygame.fastevent as eventmodule
+except ImportError:
+ import pygame.event as eventmodule
+
+
+# You can customize this if you use your
+# own events, but you must OBEY:
+#
+# USEREVENT <= TWISTEDEVENT < NUMEVENTS
+#
+TWISTEDEVENT = USEREVENT
+
+def postTwistedEvent(func):
+ # if not using pygame.fastevent, this can explode if the queue
+ # fills up.. so that's bad. Use pygame.fastevent, in pygame CVS
+ # as of 2005-04-18.
+ eventmodule.post(eventmodule.Event(TWISTEDEVENT, iterateTwisted=func))
+
+def helloWorld():
+ print "hello, world"
+ reactor.callLater(1, helloWorld)
+reactor.callLater(1, helloWorld)
+
+def twoSecondsPassed():
+ print "two seconds passed"
+reactor.callLater(2, twoSecondsPassed)
+
+def eventIterator():
+ while True:
+ yield eventmodule.wait()
+ while True:
+ event = eventmodule.poll()
+ if event.type == NOEVENT:
+ break
+ else:
+ yield event
+
+def main():
+ pygame.init()
+ if hasattr(eventmodule, 'init'):
+ eventmodule.init()
+ screen = pygame.display.set_mode((300, 300))
+
+ # send an event when twisted wants attention
+ reactor.interleave(postTwistedEvent)
+ # make shouldQuit a True value when it's safe to quit
+ # by appending a value to it. This ensures that
+ # Twisted gets to shut down properly.
+ shouldQuit = []
+ reactor.addSystemEventTrigger('after', 'shutdown', shouldQuit.append, True)
+
+ for event in eventIterator():
+ if event.type == TWISTEDEVENT:
+ event.iterateTwisted()
+ if shouldQuit:
+ break
+ elif event.type == QUIT:
+ reactor.stop()
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ reactor.stop()
+
+ pygame.quit()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/tkinterdemo.py b/doc/core/examples/tkinterdemo.py
new file mode 100644
index 0000000..1fad50e
--- /dev/null
+++ b/doc/core/examples/tkinterdemo.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of using Twisted with Tkinter.
+Displays a frame with buttons that responds to mouse clicks.
+
+Run this example by typing in:
+ python tkinterdemo.py
+"""
+
+
+from Tkinter import Tk, Frame, Button, LEFT
+from twisted.internet import reactor, tksupport
+
+
+class App(object):
+
+ def onQuit(self):
+ print "Quit!"
+ reactor.stop()
+
+ def onButton(self):
+ print "Hello!"
+
+ def __init__(self, master):
+ frame = Frame(master)
+ frame.pack()
+
+ q = Button(frame, text="Quit!", command=self.onQuit)
+ b = Button(frame, text="Hello!", command=self.onButton)
+
+ q.pack(side=LEFT)
+ b.pack(side=LEFT)
+
+
+if __name__ == '__main__':
+ root = Tk()
+ tksupport.install(root)
+ app = App(root)
+ reactor.run()
diff --git a/doc/core/examples/twistd-logging.tac b/doc/core/examples/twistd-logging.tac
new file mode 100644
index 0000000..2302558
--- /dev/null
+++ b/doc/core/examples/twistd-logging.tac
@@ -0,0 +1,33 @@
+# Invoke this script with:
+
+# $ twistd -ny twistd-logging.tac
+
+# It will create a log file named "twistd-logging.log". The log file will
+# be formatted such that each line contains the representation of the dict
+# structure of each log message.
+
+from twisted.application.service import Application
+from twisted.python.log import ILogObserver, msg
+from twisted.python.util import untilConcludes
+from twisted.internet.task import LoopingCall
+
+
+logfile = open("twistd-logging.log", "a")
+
+
+def log(eventDict):
+ # untilConcludes is necessary to retry the operation when the system call
+ # has been interrupted.
+ untilConcludes(logfile.write, "Got a log! %r\n" % eventDict)
+ untilConcludes(logfile.flush)
+
+
+def logSomething():
+ msg("A log message")
+
+
+LoopingCall(logSomething).start(1)
+
+application = Application("twistd-logging")
+application.setComponent(ILogObserver, log)
+
diff --git a/doc/core/examples/wxacceptance.py b/doc/core/examples/wxacceptance.py
new file mode 100644
index 0000000..75394eb
--- /dev/null
+++ b/doc/core/examples/wxacceptance.py
@@ -0,0 +1,113 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Acceptance tests for wxreactor.
+
+Please test on Linux, Win32 and OS X:
+1. Startup event is called at startup.
+2. Scheduled event is called after 2 seconds.
+3. Shutdown takes 3 seconds, both when quiting from menu and when closing
+ window (e.g. Alt-F4 in metacity). This tests reactor.stop() and
+ wxApp.ExitEventLoop().
+4. 'hello, world' continues to be printed even when modal dialog is open
+ (use dialog menu item), when menus are held down, when window is being
+ dragged.
+"""
+
+import sys, time
+
+try:
+ from wx import Frame as wxFrame, DefaultPosition as wxDefaultPosition, \
+ Size as wxSize, Menu as wxMenu, MenuBar as wxMenuBar, \
+ EVT_MENU, MessageDialog as wxMessageDialog, App as wxApp
+except ImportError, e:
+ from wxPython.wx import *
+
+from twisted.python import log
+from twisted.internet import wxreactor
+wxreactor.install()
+from twisted.internet import reactor, defer
+
+
+# set up so that "hello, world" is printed continously
+dc = None
+def helloWorld():
+ global dc
+ print "hello, world", time.time()
+ dc = reactor.callLater(0.1, helloWorld)
+dc = reactor.callLater(0.1, helloWorld)
+
+def twoSecondsPassed():
+ print "two seconds passed"
+
+def printer(s):
+ print s
+
+def shutdown():
+ print "shutting down in 3 seconds"
+ if dc.active():
+ dc.cancel()
+ reactor.callLater(1, printer, "2...")
+ reactor.callLater(2, printer, "1...")
+ reactor.callLater(3, printer, "0...")
+ d = defer.Deferred()
+ reactor.callLater(3, d.callback, 1)
+ return d
+
+def startup():
+ print "Start up event!"
+
+reactor.callLater(2, twoSecondsPassed)
+reactor.addSystemEventTrigger("after", "startup", startup)
+reactor.addSystemEventTrigger("before", "shutdown", shutdown)
+
+
+ID_EXIT = 101
+ID_DIALOG = 102
+
+class MyFrame(wxFrame):
+ def __init__(self, parent, ID, title):
+ wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, wxSize(300, 200))
+ menu = wxMenu()
+ menu.Append(ID_DIALOG, "D&ialog", "Show dialog")
+ menu.Append(ID_EXIT, "E&xit", "Terminate the program")
+ menuBar = wxMenuBar()
+ menuBar.Append(menu, "&File")
+ self.SetMenuBar(menuBar)
+ EVT_MENU(self, ID_EXIT, self.DoExit)
+ EVT_MENU(self, ID_DIALOG, self.DoDialog)
+ # you really ought to do this instead of reactor.stop() in
+ # DoExit, but for the sake of testing we'll let closing the
+ # window shutdown wx without reactor.stop(), to make sure that
+ # still does the right thing.
+ #EVT_CLOSE(self, lambda evt: reactor.stop())
+
+ def DoDialog(self, event):
+ dl = wxMessageDialog(self, "Check terminal to see if messages are still being "
+ "printed by Twisted.")
+ dl.ShowModal()
+ dl.Destroy()
+
+ def DoExit(self, event):
+ reactor.stop()
+
+
+class MyApp(wxApp):
+
+ def OnInit(self):
+ frame = MyFrame(None, -1, "Hello, world")
+ frame.Show(True)
+ self.SetTopWindow(frame)
+ return True
+
+
+def demo():
+ log.startLogging(sys.stdout)
+ app = MyApp(0)
+ reactor.registerWxApp(app)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ demo()
diff --git a/doc/core/examples/wxdemo.py b/doc/core/examples/wxdemo.py
new file mode 100644
index 0000000..a9afdd3
--- /dev/null
+++ b/doc/core/examples/wxdemo.py
@@ -0,0 +1,64 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""Demo of wxPython integration with Twisted."""
+
+import sys
+
+from wx import Frame, DefaultPosition, Size, Menu, MenuBar, App
+from wx import EVT_MENU, EVT_CLOSE
+
+from twisted.python import log
+from twisted.internet import wxreactor
+wxreactor.install()
+
+# import t.i.reactor only after installing wxreactor:
+from twisted.internet import reactor
+
+
+ID_EXIT = 101
+
+class MyFrame(Frame):
+ def __init__(self, parent, ID, title):
+ Frame.__init__(self, parent, ID, title, DefaultPosition, Size(300, 200))
+ menu = Menu()
+ menu.Append(ID_EXIT, "E&xit", "Terminate the program")
+ menuBar = MenuBar()
+ menuBar.Append(menu, "&File")
+ self.SetMenuBar(menuBar)
+ EVT_MENU(self, ID_EXIT, self.DoExit)
+
+ # make sure reactor.stop() is used to stop event loop:
+ EVT_CLOSE(self, lambda evt: reactor.stop())
+
+ def DoExit(self, event):
+ reactor.stop()
+
+
+class MyApp(App):
+
+ def twoSecondsPassed(self):
+ print "two seconds passed"
+
+ def OnInit(self):
+ frame = MyFrame(None, -1, "Hello, world")
+ frame.Show(True)
+ self.SetTopWindow(frame)
+ # look, we can use twisted calls!
+ reactor.callLater(2, self.twoSecondsPassed)
+ return True
+
+
+def demo():
+ log.startLogging(sys.stdout)
+
+ # register the App instance with Twisted:
+ app = MyApp(0)
+ reactor.registerWxApp(app)
+
+ # start the event loop:
+ reactor.run()
+
+
+if __name__ == '__main__':
+ demo()
diff --git a/doc/core/howto/amp.html b/doc/core/howto/amp.html
new file mode 100644
index 0000000..bf6a76c
--- /dev/null
+++ b/doc/core/howto/amp.html
@@ -0,0 +1,349 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: <b>A</b>synchronous <b>M</b>essaging <b>P</b>rotocol Overview</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title"><b>A</b>synchronous <b>M</b>essaging <b>P</b>rotocol Overview</h1>
+ <div class="toc"><ol><li><a href="#auto0">Setting Up</a></li><li><a href="#auto1">Commands</a></li><li><a href="#auto2">Locators</a></li><li><a href="#auto3">Box Receivers</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <p>The purpose of this guide is to describe the uses for and usage of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.protocols.amp.html" title="twisted.protocols.amp">twisted.protocols.amp</a></code> beyond what is explained in the API documentation. It will show you how to implement an AMP server which can respond to commands or interact directly with individual messages. It will also show you how to implement an AMP client which can issue commands to a server.</p>
+
+ <p>AMP is a bidirectional command/response-oriented protocol intended to be extended with application-specific request types and handlers. Various simple data types are supported and support for new data types can be added by applications.</p>
+
+ <h2>Setting Up<a name="auto0"/></h2>
+
+ <p>AMP runs over a stream-oriented connection-based protocol, such as TCP or SSL. Before you can use any features of the AMP protocol, you need a connection. The protocol class to use to establish an AMP connection is <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.protocols.amp.AMP.html" title="twisted.protocols.amp.AMP">AMP</a></code>. Connection setup works as it does for almost all protocols in Twisted. For example, you can set up a listening AMP server using a server endpoint:</p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">amp</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">AMP</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">endpoints</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">TCP4ServerEndpoint</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">service</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Application</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">StreamServerEndpointService</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;basic AMP server&quot;</span>)
+
+<span class="py-src-variable">endpoint</span> = <span class="py-src-variable">TCP4ServerEndpoint</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">8750</span>)
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Factory</span>()
+<span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">AMP</span>
+<span class="py-src-variable">service</span> = <span class="py-src-variable">StreamServerEndpointService</span>(<span class="py-src-variable">endpoint</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">service</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+</pre><div class="caption">Source listing - <a href="listings/amp/basic_server.tac"><span class="filename">listings/amp/basic_server.tac</span></a></div></div>
+
+ <p>And you can connect to an AMP server using a client endpoint:</p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">basic_client</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">SystemExit</span>(<span class="py-src-variable">basic_client</span>.<span class="py-src-variable">main</span>())
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">startLogging</span>, <span class="py-src-variable">err</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">amp</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">AMP</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">endpoints</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">TCP4ClientEndpoint</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">connect</span>():
+ <span class="py-src-variable">endpoint</span> = <span class="py-src-variable">TCP4ClientEndpoint</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-string">&quot;127.0.0.1&quot;</span>, <span class="py-src-number">8750</span>)
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">Factory</span>()
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">AMP</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">endpoint</span>.<span class="py-src-variable">connect</span>(<span class="py-src-variable">factory</span>)
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">startLogging</span>(<span class="py-src-variable">stdout</span>)
+
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">connect</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">err</span>, <span class="py-src-string">&quot;Connection failed&quot;</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">done</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">done</span>)
+
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/amp/basic_client.py"><span class="filename">listings/amp/basic_client.py</span></a></div></div>
+
+ <h2>Commands<a name="auto1"/></h2>
+
+ <p>Either side of an AMP connection can issue a command to the other side. Each kind of command is represented as a subclass of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.protocols.amp.Command.html" title="twisted.protocols.amp.Command">Command</a></code>. A <code>Command</code> defines arguments, response values, and error conditions.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">amp</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Integer</span>, <span class="py-src-variable">String</span>, <span class="py-src-variable">Unicode</span>, <span class="py-src-variable">Command</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UsernameUnavailable</span>(<span class="py-src-parameter">Exception</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RegisterUser</span>(<span class="py-src-parameter">Command</span>):
+ <span class="py-src-variable">arguments</span> = [(<span class="py-src-string">'username'</span>, <span class="py-src-variable">Unicode</span>()),
+ (<span class="py-src-string">'publickey'</span>, <span class="py-src-variable">String</span>())]
+
+ <span class="py-src-variable">response</span> = [(<span class="py-src-string">'uid'</span>, <span class="py-src-variable">Integer</span>())]
+
+ <span class="py-src-variable">errors</span> = {<span class="py-src-variable">UsernameUnavailable</span>: <span class="py-src-string">'username-unavailable'</span>}
+</pre>
+
+ <p>The definition of the command's signature - its arguments, response, and possible error conditions - is separate from the implementation of the behavior to execute when the command is received. The <code>Command</code> subclass only defines the former.</p>
+
+ <p>Commands are issued by calling <code>callRemote</code> on either side of the connection. This method returns a <code>Deferred</code> which eventually fires with the result of the command.</p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+</p><span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">command_client</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">SystemExit</span>(<span class="py-src-variable">command_client</span>.<span class="py-src-variable">main</span>())
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">startLogging</span>, <span class="py-src-variable">err</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">amp</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Integer</span>, <span class="py-src-variable">String</span>, <span class="py-src-variable">Unicode</span>, <span class="py-src-variable">Command</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">basic_client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">connect</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UsernameUnavailable</span>(<span class="py-src-parameter">Exception</span>):
+ <span class="py-src-keyword">pass</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RegisterUser</span>(<span class="py-src-parameter">Command</span>):
+ <span class="py-src-variable">arguments</span> = [(<span class="py-src-string">'username'</span>, <span class="py-src-variable">Unicode</span>()),
+ (<span class="py-src-string">'publickey'</span>, <span class="py-src-variable">String</span>())]
+
+ <span class="py-src-variable">response</span> = [(<span class="py-src-string">'uid'</span>, <span class="py-src-variable">Integer</span>())]
+
+ <span class="py-src-variable">errors</span> = {<span class="py-src-variable">UsernameUnavailable</span>: <span class="py-src-string">'username-unavailable'</span>}
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">startLogging</span>(<span class="py-src-variable">stdout</span>)
+
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">connect</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">protocol</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">protocol</span>.<span class="py-src-variable">callRemote</span>(
+ <span class="py-src-variable">RegisterUser</span>,
+ <span class="py-src-variable">username</span>=<span class="py-src-string">u'alice'</span>,
+ <span class="py-src-variable">publickey</span>=<span class="py-src-string">'ssh-rsa AAAAB3NzaC1yc2 alice@actinium'</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">registered</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Registration result:'</span>, <span class="py-src-variable">result</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">registered</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">err</span>, <span class="py-src-string">&quot;Failed to register&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">finished</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">finished</span>)
+
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/amp/command_client.py"><span class="filename">listings/amp/command_client.py</span></a></div></div>
+
+ <h2>Locators<a name="auto2"/></h2>
+
+
+ <p>The logic for handling a command can be specified as an object separate from the <code>AMP</code> instance which interprets and formats bytes over the network.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">amp</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">CommandLocator</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">filepath</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePath</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UsernameUnavailable</span>(<span class="py-src-parameter">Exception</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserRegistration</span>(<span class="py-src-parameter">CommandLocator</span>):
+ <span class="py-src-variable">uidCounter</span> = <span class="py-src-number">0</span>
+
+ @<span class="py-src-variable">RegisterUser</span>.<span class="py-src-variable">responder</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">register</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>, <span class="py-src-parameter">publickey</span>):
+ <span class="py-src-variable">path</span> = <span class="py-src-variable">FilePath</span>(<span class="py-src-variable">username</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>.<span class="py-src-variable">exists</span>():
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">UsernameUnavailable</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">uidCounter</span> += <span class="py-src-number">1</span>
+ <span class="py-src-variable">path</span>.<span class="py-src-variable">setContent</span>(<span class="py-src-string">'%d %s\n'</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">uidCounter</span>, <span class="py-src-variable">publickey</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">uidCounter</span>
+</pre>
+
+ <p>When you define a separate <code>CommandLocator</code> subclass, use it by passing an instance of it to the <code>AMP</code> initializer.</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">factory</span> = <span class="py-src-variable">Factory</span>()
+<span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">AMP</span>(<span class="py-src-variable">locator</span>=<span class="py-src-variable">UserRegistration</span>())
+</pre>
+
+ <p>If no locator is passed in, <code>AMP</code> acts as its own locator. Command responders can be defined on an <code>AMP</code> subclass, just as the responder was defined on the <code>UserRegistration</code> example above.</p>
+
+ <h2>Box Receivers<a name="auto3"/></h2>
+
+ <p>AMP conversations consist of an exchange of messages called <em>boxes</em>. A <em>box</em> consists of a sequence of pairs of key and value (for example, the pair <code>username</code> and <code>alice</code>). Boxes are generally represented as <code>dict</code> instances. Normally boxes are passed back and forth to implement the command request/response features described above. The logic for handling each box can be specified as an object separate from the <code>AMP</code> instance.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">amp</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IBoxReceiver</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">BoxReflector</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IBoxReceiver</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startReceivingBoxes</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">boxSender</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">boxSender</span> = <span class="py-src-variable">boxSender</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">ampBoxReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">box</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">boxSender</span>.<span class="py-src-variable">sendBox</span>(<span class="py-src-variable">box</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopReceivingBoxes</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">boxSender</span> = <span class="py-src-variable">None</span>
+</pre>
+
+ <p>These methods parallel those of <code>IProtocol</code>. Startup notification is given by <code>startReceivingBoxes</code>. The argument passed to it is an <code>IBoxSender</code> provider, which can be used to send boxes back out over the network. <code>ampBoxReceived</code> delivers notification for a complete box having been received. And last, <code>stopReceivingBoxes</code> notifies the object that no more boxes will be received and no more can be sent. The argument passed to it is a <code>Failure</code> which may contain details about what caused the conversation to end.</p>
+
+ <p>To use a custom <code>IBoxReceiver</code>, pass it to the <code>AMP</code> initializer.</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">factory</span> = <span class="py-src-variable">Factory</span>()
+<span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">AMP</span>(<span class="py-src-variable">boxReceiver</span>=<span class="py-src-variable">BoxReflector</span>())
+</pre>
+
+ <p>If no box receiver is passed in, <code>AMP</code> acts as its own box receiver. It handles boxes by treating them as command requests or responses and delivering them to the appropriate responder or as a result to a <code>callRemote</code> <code>Deferred</code>.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/application.html b/doc/core/howto/application.html
new file mode 100644
index 0000000..7b8f186
--- /dev/null
+++ b/doc/core/howto/application.html
@@ -0,0 +1,398 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using the Twisted Application Framework</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using the Twisted Application Framework</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><ul><li><a href="#auto1">Audience</a></li><li><a href="#auto2">Goals</a></li></ul><li><a href="#auto3">Overview</a></li><li><a href="#auto4">Using application</a></li><ul><li><a href="#auto5">twistd and tac</a></li><li><a href="#auto6">Customizing twistd logging</a></li><li><a href="#auto7">Services provided by Twisted</a></li><li><a href="#auto8">Service Collection</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<h3>Audience<a name="auto1"/></h3>
+
+<p>The target audience of this document is a Twisted user who wants to deploy a
+significant amount of Twisted code in a re-usable, standard and easily
+configurable fashion. A Twisted user who wishes to use the Application
+framework needs to be familiar with developing Twisted <a href="servers.html" shape="rect">servers</a> and/or <a href="clients.html" shape="rect">clients</a>.</p>
+
+<h3>Goals<a name="auto2"/></h3>
+
+<ul>
+ <li>To introduce the Twisted Application infrastructure.</li>
+
+ <li>To explain how to deploy your Twisted application using <code>.tac</code>
+ files and <code>twistd</code></li>
+
+ <li>To outline the existing Twisted services.</li>
+</ul>
+
+<h2>Overview<a name="auto3"/></h2>
+
+<p>The Twisted Application infrastructure takes care of running and stopping
+your application. Using this infrastructure frees you from from having to
+write a large amount of boilerplate code by hooking your application into
+existing tools that manage daemonization, logging, <a href="choosing-reactor.html" shape="rect">choosing a reactor</a> and more.</p>
+
+<p>The major tool that manages Twisted applications is a command-line utility
+called <code>twistd</code>. <code>twistd</code> is cross platform, and is the
+recommended tool for running Twisted applications. </p>
+
+
+<p>The core component of the Twisted Application infrastructure is the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">twisted.application.service.Application</a></code> object — an
+object which represents your application. However, Application doesn't provide
+anything that you'd want to manipulate directly. Instead, Application acts as
+a container of any <q>Services</q> (objects implementing <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code>) that your application
+provides. Most of your interaction with the Application infrastructure will be
+done through Services.</p>
+
+<p>By <q>Service</q>, we mean anything in your application that can be started
+and stopped. Typical services include web servers, FTP servers and SSH
+clients. Your Application object can contain many services, and can even
+contain structured hierarchies of Services using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IServiceCollection.html" title="twisted.application.service.IServiceCollection">IServiceCollection</a></code>s.</p>
+
+<p>Here's a simple example of constructing an Application object which
+represents an echo server that runs on TCP port 7001.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">somemodule</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">EchoFactory</span>
+
+<span class="py-src-variable">port</span> = <span class="py-src-number">7001</span>
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">EchoFactory</span>()
+
+<span class="py-src-comment"># this is the important bit</span>
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;echo&quot;</span>) <span class="py-src-comment"># create the Application</span>
+<span class="py-src-variable">echoService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-variable">port</span>, <span class="py-src-variable">factory</span>) <span class="py-src-comment"># create the service</span>
+<span class="py-src-comment"># add the service to the application</span>
+<span class="py-src-variable">echoService</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+</pre>
+
+<p>See <a href="servers.html" shape="rect">Writing Servers</a> for an explanation of
+EchoFactory.</p>
+
+<p>This example creates a simple hierarchy:
+<pre xml:space="preserve">
+ application
+ |
+ `- echoService
+</pre> More complicated hierarchies of services can be created using
+IServiceCollection. You will most likely want to do this to manage Services
+which are dependent on other Services. For example, a proxying Twisted
+application might want its server Service to only start up after the associated
+Client service. </p>
+
+
+<h2>Using application<a name="auto4"/></h2>
+
+<h3>twistd and tac<a name="auto5"/></h3><a name="twistd" shape="rect"/>
+
+<p>To handle start-up and configuration of your Twisted application, the
+Twisted Application infrastructure uses <code>.tac</code> files.
+<code>.tac</code> are Python files which configure an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code> object and assign this
+object to the top-level variable <q><code>application</code></q>.</p>
+
+<p>The following is a simple example of a <code>.tac</code> file:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+</p><span class="py-src-comment"># You can run this .tac file directly with:</span>
+<span class="py-src-comment"># twistd -ny service.tac</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+This is an example .tac file which starts a webserver on port 8080 and
+serves files from the current working directory.
+
+The important part of this, the part that makes it a .tac file, is
+the final root-level section, which sets up the object called 'application'
+which twistd will look for
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getWebService</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a service suitable for creating an application object.
+
+ This service is a simple web server that serves files on port 8080 from
+ underneath the current working directory.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-comment"># create a resource to serve static files</span>
+ <span class="py-src-variable">fileServer</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">getcwd</span>()))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8080</span>, <span class="py-src-variable">fileServer</span>)
+
+<span class="py-src-comment"># this is the core part of any tac file, the creation of the root-level</span>
+<span class="py-src-comment"># application object</span>
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;Demo application&quot;</span>)
+
+<span class="py-src-comment"># attach the service to its parent application</span>
+<span class="py-src-variable">service</span> = <span class="py-src-variable">getWebService</span>()
+<span class="py-src-variable">service</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+</pre><div class="caption">Source listing - <a href="listings/application/service.tac"><span class="filename">listings/application/service.tac</span></a></div></div>
+
+<p><code>twistd</code> is a program that runs Twisted applications using a
+<code>.tac</code> file. In its most simple form, it takes a single argument
+<code>-y</code> and a tac file name. For example, you can run the above server
+with the command <code class="shell">twistd -y service.tac</code>.</p>
+
+<p>By default, <code>twistd</code> daemonizes and logs to a file called
+<code>twistd.log</code>. More usually, when debugging, you will want your
+application to run in the foreground and log to the command line. To run the
+above file like this, use the command <code class="shell">twistd -noy
+service.tac</code></p>
+
+<p>For more information, see the <code>twistd</code> man page.</p>
+
+<h3>Customizing <code>twistd</code> logging<a name="auto6"/></h3>
+
+<p>
+<code>twistd</code> logging can be customized using the command
+line. This requires that a <em>log observer factory</em> be
+importable. Given a file named <code>my.py</code> with the code:
+</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FileLogObserver</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">logger</span>():
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">FileLogObserver</span>(<span class="py-src-variable">open</span>(<span class="py-src-string">&quot;/tmp/my.log&quot;</span>, <span class="py-src-string">&quot;w&quot;</span>)).<span class="py-src-variable">emit</span>
+</pre>
+
+<p>
+invoking <code class="shell">twistd --logger my.logger ...</code> will log
+to a file named <code>/tmp/my.log</code> (this simple example could easily be
+replaced with use of the <code>--logfile</code> parameter to twistd).
+</p>
+
+<p>
+Alternatively, the logging behavior can be customized through an API
+accessible from <code>.tac</code> files. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.ILogObserver.html" title="twisted.python.log.ILogObserver">ILogObserver</a></code> component can be
+set on an Application in order to customize the default log observer that
+<code>twistd</code> will use.
+</p>
+
+<p>
+Here is an example of how to use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.logfile.DailyLogFile.html" title="twisted.python.logfile.DailyLogFile">DailyLogFile</a></code>, which rotates the log once
+per day.
+</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">service</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Application</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ILogObserver</span>, <span class="py-src-variable">FileLogObserver</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">logfile</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DailyLogFile</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;myapp&quot;</span>)
+<span class="py-src-variable">logfile</span> = <span class="py-src-variable">DailyLogFile</span>(<span class="py-src-string">&quot;my.log&quot;</span>, <span class="py-src-string">&quot;/tmp&quot;</span>)
+<span class="py-src-variable">application</span>.<span class="py-src-variable">setComponent</span>(<span class="py-src-variable">ILogObserver</span>, <span class="py-src-variable">FileLogObserver</span>(<span class="py-src-variable">logfile</span>).<span class="py-src-variable">emit</span>)
+</pre>
+
+<p>
+invoking <code class="shell">twistd -y my.tac</code> will create a log file
+at <code>/tmp/my.log</code>.
+</p>
+
+<h3>Services provided by Twisted<a name="auto7"/></h3>
+
+<p>Twisted provides several services that you want to know about.</p>
+
+<p>Each of these services (except TimerService) has a corresponding
+<q>connect</q> or <q>listen</q> method on the reactor, and the constructors for
+the services take the same arguments as the reactor methods. The
+<q>connect</q> methods are for clients and the <q>listen</q> methods are for
+servers. For example, TCPServer corresponds to reactor.listenTCP and TCPClient
+corresponds to reactor.connectTCP. </p>
+
+<dl>
+ <dt><code>TCPServer</code>
+ </dt>
+
+ <dt><code>TCPClient</code>
+ </dt>
+
+ <dd>
+ Services which allow you to make connections and listen for connections
+ on TCP ports.
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTCP.listenTCP.html" title="twisted.internet.interfaces.IReactorTCP.listenTCP">listenTCP</a></code></li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTCP.connectTCP.html" title="twisted.internet.interfaces.IReactorTCP.connectTCP">connectTCP</a></code></li>
+ </ul>
+ </dd>
+
+ <dt><code>UNIXServer</code></dt>
+
+ <dt><code>UNIXClient</code></dt>
+
+ <dd>
+ Services which listen and make connections over UNIX sockets.
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUNIX.listenUNIX.html" title="twisted.internet.interfaces.IReactorUNIX.listenUNIX">listenUNIX</a></code></li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUNIX.connectUNIX.html" title="twisted.internet.interfaces.IReactorUNIX.connectUNIX">connectUNIX</a></code></li>
+ </ul>
+ </dd>
+
+ <dt><code>SSLServer</code></dt>
+
+ <dt><code>SSLClient</code></dt>
+
+ <dd>Services which allow you to make SSL connections and run SSL servers.
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorSSL.listenSSL.html" title="twisted.internet.interfaces.IReactorSSL.listenSSL">listenSSL</a></code></li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorSSL.connectSSL.html" title="twisted.internet.interfaces.IReactorSSL.connectSSL">connectSSL</a></code></li>
+ </ul>
+ </dd>
+
+ <dt><code>UDPServer</code></dt>
+
+ <dt><code>UDPClient</code></dt>
+
+ <dd>Services which allow you to send and receive data over UDP
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUDP.listenUDP.html" title="twisted.internet.interfaces.IReactorUDP.listenUDP">listenUDP</a></code></li>
+ </ul>
+
+ <p>See also the <a href="udp.html" shape="rect">UDP documentation</a>.</p>
+ </dd>
+
+ <dt><code>UNIXDatagramServer</code></dt>
+
+ <dt><code>UNIXDatagramClient</code></dt>
+
+ <dd>Services which send and receive data over UNIX datagram sockets.
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUNIXDatagram.listenUNIXDatagram.html" title="twisted.internet.interfaces.IReactorUNIXDatagram.listenUNIXDatagram">listenUNIXDatagram</a></code></li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUNIXDatagram.connectUNIXDatagram.html" title="twisted.internet.interfaces.IReactorUNIXDatagram.connectUNIXDatagram">connectUNIXDatagram</a></code></li>
+ </ul>
+ </dd>
+
+ <dt><code>MulticastServer</code></dt>
+
+ <dd>
+ A server for UDP socket methods that support multicast.
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorMulticast.listenMulticast.html" title="twisted.internet.interfaces.IReactorMulticast.listenMulticast">listenMulticast</a></code></li>
+ </ul>
+ </dd>
+
+ <dt><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.internet.TimerService.html" title="twisted.application.internet.TimerService">TimerService</a></code></dt>
+
+ <dd>
+ A service to periodically call a function.
+ </dd>
+
+</dl>
+
+<h3>Service Collection<a name="auto8"/></h3>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IServiceCollection.html" title="twisted.application.service.IServiceCollection">IServiceCollection</a></code> objects contain
+<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code> objects.
+IService objects can be added to IServiceCollection by calling <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IService.setServiceParent.html" title="twisted.application.service.IService.setServiceParent">setServiceParent</a></code> and detached
+by using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IService.disownServiceParent.html" title="twisted.application.service.IService.disownServiceParent">disownServiceParent</a></code>.</p>
+
+<p>The standard implementation of IServiceCollection is <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.MultiService.html" title="twisted.application.service.MultiService">MultiService</a></code>, which also implements
+IService. MultiService is useful for creating a new Service which combines two
+or more existing Services. For example, you could create a DNS Service as a
+MultiService which has a TCP and a UDP Service as children.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">names</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span>, <span class="py-src-variable">dns</span>, <span class="py-src-variable">hosts</span>
+
+<span class="py-src-variable">port</span> = <span class="py-src-number">53</span>
+
+<span class="py-src-comment"># Create a MultiService, and hook up a TCPServer and a UDPServer to it as</span>
+<span class="py-src-comment"># children.</span>
+<span class="py-src-variable">dnsService</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">MultiService</span>()
+<span class="py-src-variable">hostsResolver</span> = <span class="py-src-variable">hosts</span>.<span class="py-src-variable">Resolver</span>(<span class="py-src-string">'/etc/hosts'</span>)
+<span class="py-src-variable">tcpFactory</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">DNSServerFactory</span>([<span class="py-src-variable">hostsResolver</span>])
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-variable">port</span>, <span class="py-src-variable">tcpFactory</span>).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">dnsService</span>)
+<span class="py-src-variable">udpFactory</span> = <span class="py-src-variable">dns</span>.<span class="py-src-variable">DNSDatagramProtocol</span>(<span class="py-src-variable">tcpFactory</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">UDPServer</span>(<span class="py-src-variable">port</span>, <span class="py-src-variable">udpFactory</span>).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">dnsService</span>)
+
+<span class="py-src-comment"># Create an application as normal</span>
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;DNSExample&quot;</span>)
+
+<span class="py-src-comment"># Connect our MultiService to the application, just like a normal service.</span>
+<span class="py-src-variable">dnsService</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+</pre>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/basics.html b/doc/core/howto/basics.html
new file mode 100644
index 0000000..86970d7
--- /dev/null
+++ b/doc/core/howto/basics.html
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Basics</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Basics</h1>
+ <div class="toc"><ol><li><a href="#auto0">Application</a></li><li><a href="#auto1">twistd</a></li><li><a href="#auto2">OS Integration</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Application<a name="auto0"/></h2>
+
+<p>Twisted programs usually work
+with <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">twisted.application.service.Application</a></code>.
+This class usually holds all persistent configuration of a running
+server -- ports to bind to, places where connections to must be kept
+or attempted, periodic actions to do and almost everything else. It is
+the root object in a tree of services implementing <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code>.</p>
+
+<p>Other HOWTOs describe how to write custom code for Applications,
+but this one describes how to use already written code (which can be
+part of Twisted or from a third-party Twisted plugin developer). The
+Twisted distribution comes with an important tool to deal with
+Applications, <code>twistd</code>.</p>
+
+<p><code>Application</code>s are just Python objects, which can
+be created and manipulated in the same ways as any other object.
+</p>
+
+<h2>twistd<a name="auto1"/></h2><a name="twistd" shape="rect"/>
+
+<p>The Twisted Daemon is a program that knows how to run Applications.
+This program
+is <code class="shell">twistd(1)</code>. Strictly
+speaking, <code class="shell">twistd</code> is not necessary --
+fetching the application, getting the <code>IService</code> component,
+calling <code>startService</code>, scheduling <code>stopService</code> when
+the reactor shuts down, and then calling <code>reactor.run()</code> could be
+done manually. <code class="shell">twistd(1)</code>, however, supplies
+many options which are highly useful for program set up.</p>
+
+<p><code class="shell">twistd</code> supports choosing a reactor (for more on
+reactors, see <a href="choosing-reactor.html" shape="rect">Choosing a Reactor</a>), logging
+to a logfile, daemonizing and more. <code class="shell">twistd</code> supports all
+Applications mentioned above -- and an additional one. Sometimes
+it is convenient to write the code for building a class in straight
+Python. One big source of such Python files is the <code>doc/examples</code>
+directory. When a straight Python file which defines an <code>Application</code>
+object called <code>application</code> is used, use the <code class="shell">-y</code>
+option.</p>
+
+<p>When <code class="shell">twistd</code> runs, it records its process
+id in a <code>twistd.pid</code> file (this can be configured via a
+command line switch). In order to shutdown
+the <code class="shell">twistd</code> process, kill that pid (usually
+you would do <code class="shell">kill `cat twistd.pid`</code>).
+</p>
+
+<p>As always, the gory details are in the manual page.</p>
+
+<h2>OS Integration<a name="auto2"/></h2>
+
+<p>
+If you have an Application that runs
+with <code class="shell">twistd</code>, you can easily deploy it on
+RedHat Linux or Debian GNU/Linux based systems using
+the <code class="shell">tap2deb</code>
+or <code class="shell">tap2rpm</code> tools. These take a Twisted
+Application file (of any of the supported formats — Python source, XML
+or pickle), and build a Debian or RPM package (respectively) that
+installs the Application as a system service. The package includes the
+Application file, a default <code>/etc/init.d/</code> script that
+starts and stops the process with twistd, and post-installation
+scripts that configure the Application to be run in the appropriate
+init levels.
+</p>
+
+
+<div class="note"><strong>Note: </strong> <code class="shell">tap2rpm</code>
+and <code class="shell">tap2deb</code> do not package your entire
+application and dependent code, just the Twisted Application file. You
+will need to find some other way to package your Python code, such
+as <a href="http://docs.python.org/library/distutils.html" shape="rect">distutils</a>'
+<code>bdist_rpm</code> command.
+</div>
+
+<p>
+For more savvy users, these tools also generate the source package, allowing
+you to modify and polish things which automated software cannot detect (such as
+dependencies or relationships to virtual packages).
+</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/book.tex b/doc/core/howto/book.tex
new file mode 100644
index 0000000..2708619
--- /dev/null
+++ b/doc/core/howto/book.tex
@@ -0,0 +1,129 @@
+\documentclass[oneside]{book}
+\usepackage[dvips]{graphicx}
+\usepackage{times,mathptmx}
+\usepackage{ifthen}
+\usepackage{hyperref}
+
+\usepackage{geometry}
+\geometry{verbose,letterpaper,tmargin=1in,bmargin=0.5in,lmargin=1in,rmargin=1in}
+
+\setlength{\oddsidemargin}{0in}
+\setlength{\textwidth}{\paperwidth}
+\addtolength{\textwidth}{-2in}
+
+\newcommand{\loreref}[1]{%
+ \ifthenelse{\value{page}=\pageref{#1}}%
+ { (this page)}%
+ { (page \pageref{#1})}%
+}
+
+
+\title{The Twisted Documentation}
+\author{The Twisted Development Team}
+
+\tolerance=1000
+\sloppy
+
+\begin{document}
+\maketitle
+\tableofcontents
+
+\chapter{Introduction}
+
+\input{vision.tex}
+
+
+\chapter{Getting Started}
+
+\input{servers.tex}
+\input{clients.tex}
+\input{trial.tex}
+\input{tutorial/index.tex}
+\input{tutorial/intro.tex}
+\input{tutorial/protocol.tex}
+\input{tutorial/style.tex}
+\input{tutorial/components.tex}
+\input{tutorial/backends.tex}
+\input{tutorial/web.tex}
+\input{tutorial/pb.tex}
+\input{tutorial/factory.tex}
+\input{tutorial/client.tex}
+\input{tutorial/library.tex}
+\input{tutorial/configuration.tex}
+\input{quotes.tex}
+\input{design.tex}
+
+
+\chapter{Networking and Other Event Sources}
+
+\input{internet-overview.tex}
+\input{reactor-basics.tex}
+\input{ssl.tex}
+\input{udp.tex}
+\input{process.tex}
+\input{defer.tex}
+\input{gendefer.tex}
+\input{time.tex}
+\input{threading.tex}
+\input{producers.tex}
+\input{choosing-reactor.tex}
+
+
+\chapter{High-Level Infrastructure}
+
+\input{endpoints.tex}
+\input{components.tex}
+\input{cred.tex}
+\input{plugin.tex}
+
+
+\chapter{Deploying Twisted Applications}
+
+\input{basics.tex}
+\input{application.tex}
+\input{tap.tex}
+
+
+\chapter{Utilities}
+
+\input{logging.tex}
+\input{constants.tex}
+\input{rdbms.tex}
+\input{options.tex}
+\input{dirdbm.tex}
+\input{testing.tex}
+
+
+\chapter{Asynchronous Messaging Protocol (AMP)}
+
+\input{amp.tex}
+
+
+\chapter{Perspective Broker}
+
+\input{pb.tex}
+\input{pb-intro.tex}
+\input{pb-usage.tex}
+\input{pb-clients.tex}
+\input{pb-copyable.tex}
+\input{pb-cred.tex}
+\input{pb-limits.tex}
+
+
+\chapter{Manual Pages}
+
+\input{../man/trial-man.tex}
+\clearpage
+\input{../man/twistd-man.tex}
+\clearpage
+\input{../man/tap2deb-man.tex}
+\clearpage
+\input{../man/tap2rpm-man.tex}
+
+
+\chapter{Appendix}
+
+\input{glossary.tex}
+\input{debug-with-emacs.tex}
+
+\end{document}
diff --git a/doc/core/howto/choosing-reactor.html b/doc/core/howto/choosing-reactor.html
new file mode 100644
index 0000000..22ea479
--- /dev/null
+++ b/doc/core/howto/choosing-reactor.html
@@ -0,0 +1,395 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Choosing a Reactor and GUI Toolkit Integration</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Choosing a Reactor and GUI Toolkit Integration</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Reactor Functionality</a></li><li><a href="#auto2">General Purpose Reactors</a></li><ul><li><a href="#auto3">Select()-based Reactor</a></li></ul><li><a href="#auto4">Platform-Specific Reactors</a></li><ul><li><a href="#auto5">Poll-based Reactor</a></li><li><a href="#auto6">KQueue</a></li><li><a href="#auto7">WaitForMultipleObjects (WFMO) for Win32</a></li><li><a href="#auto8">Input/Output Completion Port (IOCP) for Win32</a></li><li><a href="#auto9">Epoll-based Reactor</a></li></ul><li><a href="#auto10">GUI Integration Reactors</a></li><ul><li><a href="#auto11">GTK+</a></li><li><a href="#auto12">GTK+ 3.0 and GObject Introspection</a></li><li><a href="#auto13">wxPython</a></li><li><a href="#auto14">CoreFoundation</a></li></ul><li><a href="#auto15">Non-Reactor GUI Integration</a></li><ul><li><a href="#auto16">Tkinter</a></li><li><a href="#auto17">PyUI</a></li></ul></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>Twisted provides a variety of implementations of the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">twisted.internet.reactor</a></code>. The specialized
+ implementations are suited for different purposes and are
+ designed to integrate better with particular platforms.</p>
+
+ <p>The <a href="#epoll" shape="rect">epoll()-based reactor</a> is Twisted's default on
+ Linux. Other platforms use <a href="#poll" shape="rect">poll()</a>, or the most
+ cross-platform reactor, <a href="#select" shape="rect">select()</a>.</p>
+
+ <p>Platform-specific reactor implementations exist for:</p>
+
+ <ul>
+ <li><a href="#poll" shape="rect">Poll for Linux</a></li>
+ <li><a href="#epoll" shape="rect">Epoll for Linux 2.6</a></li>
+ <li><a href="#win32_wfmo" shape="rect">WaitForMultipleObjects (WFMO) for Win32</a></li>
+ <li><a href="#win32_iocp" shape="rect">Input/Output Completion Port (IOCP) for Win32</a></li>
+ <li><a href="#kqueue" shape="rect">KQueue for FreeBSD and Mac OS X</a></li>
+ <li><a href="#cfreactor" shape="rect">CoreFoundation for Mac OS X</a></li>
+ </ul>
+
+ <p>The remaining custom reactor implementations provide support
+ for integrating with the native event loops of various graphical
+ toolkits. This lets your Twisted application use all of the
+ usual Twisted APIs while still being a graphical application.</p>
+
+ <p>Twisted currently integrates with the following graphical
+ toolkits:</p>
+
+ <ul>
+ <li><a href="#gtk" shape="rect">GTK+ 2.0</a></li>
+ <li><a href="#gtk3" shape="rect">GTK+ 3.0 and GObject Introspection</a></li>
+ <li><a href="#tkinter" shape="rect">Tkinter</a></li>
+ <li><a href="#wxpython" shape="rect">wxPython</a></li>
+ <li><a href="#win32_wfmo" shape="rect">Win32</a></li>
+ <li><a href="#cfreactor" shape="rect">CoreFoundation</a></li>
+ <li><a href="#pyui" shape="rect">PyUI</a></li>
+ </ul>
+
+ <p>When using applications that are runnable using <code>twistd</code>, e.g.
+ TACs or plugins, there is no need to choose a reactor explicitly, since
+ this can be chosen using <code>twistd</code>'s -r option.</p>
+
+ <p>In all cases, the event loop is started by calling <code class="python">reactor.run()</code>. In all cases, the event loop
+ should be stopped with <code class="python">reactor.stop()</code>.</p>
+
+ <p><strong>IMPORTANT:</strong> installing a reactor should be the first thing
+ done in the app, since any code that does
+ <code class="python">from twisted.internet import reactor</code> will automatically
+ install the default reactor if the code hasn't already installed one.</p>
+
+ <h2>Reactor Functionality<a name="auto1"/></h2>
+
+ <table border="1" cellpadding="7" cellspacing="0" title="Summary of reactor features">
+ <tr><td colspan="1" rowspan="1"/><th colspan="1" rowspan="1">Status</th><th colspan="1" rowspan="1">TCP</th><th colspan="1" rowspan="1">SSL</th><th colspan="1" rowspan="1">UDP</th><th colspan="1" rowspan="1">Threading</th><th colspan="1" rowspan="1">Processes</th><th colspan="1" rowspan="1">Scheduling</th><th colspan="1" rowspan="1">Platforms</th></tr>
+ <tr><th colspan="1" rowspan="1">select()</th><td colspan="1" rowspan="1">Stable</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Unix, Win32</td></tr>
+ <tr><th colspan="1" rowspan="1">poll</th><td colspan="1" rowspan="1">Stable</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Unix</td></tr>
+ <tr><th colspan="1" rowspan="1">WaitForMultipleObjects (WFMO) for Win32</th><td colspan="1" rowspan="1">Experimental</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Win32</td></tr>
+ <tr><th colspan="1" rowspan="1">Input/Output Completion Port (IOCP) for Win32</th><td colspan="1" rowspan="1">Experimental</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Win32</td></tr>
+ <tr><th colspan="1" rowspan="1">CoreFoundation</th><td colspan="1" rowspan="1">Unmaintained</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Mac OS X</td></tr>
+ <tr><th colspan="1" rowspan="1">epoll</th><td colspan="1" rowspan="1">Stable</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Linux 2.6</td></tr>
+ <tr><th colspan="1" rowspan="1">GTK+</th><td colspan="1" rowspan="1">Stable</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Unix, Win32</td></tr>
+ <tr><th colspan="1" rowspan="1">wx</th><td colspan="1" rowspan="1">Experimental</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Unix, Win32</td></tr>
+ <tr><th colspan="1" rowspan="1">kqueue</th><td colspan="1" rowspan="1">Experimental</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">FreeBSD</td></tr>
+ </table>
+
+ <h2>General Purpose Reactors<a name="auto2"/></h2>
+
+ <h3>Select()-based Reactor<a name="auto3"/></h3><a name="select" shape="rect"/>
+
+ <p>The <code>select</code> reactor is the default on platforms that don't
+ provide a better alternative that covers all use cases. If
+ the <code>select</code> reactor is desired, it may be installed via:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">selectreactor</span>
+<span class="py-src-variable">selectreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h2>Platform-Specific Reactors<a name="auto4"/></h2>
+
+ <h3>Poll-based Reactor<a name="auto5"/></h3><a name="poll" shape="rect"/>
+
+ <p>The PollReactor will work on any platform that provides <code class="python">select.poll</code>. With larger numbers of connected
+ sockets, it may provide for better performance than the SelectReactor.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pollreactor</span>
+<span class="py-src-variable">pollreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h3>KQueue<a name="auto6"/></h3><a name="kqueue" shape="rect"/>
+
+ <p>The KQueue Reactor allows Twisted to use FreeBSD's kqueue mechanism for
+ event scheduling. See instructions in the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.kqreactor.html" title="twisted.internet.kqreactor">twisted.internet.kqreactor</a></code>'s
+ docstring for installation notes.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">kqreactor</span>
+<span class="py-src-variable">kqreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+
+ <h3>WaitForMultipleObjects (WFMO) for Win32<a name="auto7"/></h3><a name="win32_wfmo" shape="rect"/>
+
+ <p>The Win32 reactor is not yet complete and has various limitations
+ and issues that need to be addressed. The reactor supports GUI integration
+ with the win32gui module, so it can be used for native Win32 GUI applications.
+ </p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">win32eventreactor</span>
+<span class="py-src-variable">win32eventreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h3>Input/Output Completion Port (IOCP) for Win32<a name="auto8"/></h3><a name="win32_iocp" shape="rect"/>
+
+ <p>
+ Windows provides a fast, scalable event notification system known as IO
+ Completion Ports, or IOCP for short. Twisted includes a reactor based
+ on IOCP which is nearly complete.
+ </p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">iocpreactor</span>
+<span class="py-src-variable">iocpreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h3>Epoll-based Reactor<a name="auto9"/></h3><a name="epoll" shape="rect"/>
+
+ <p>The EPollReactor will work on any platform that provides
+ <code class="python">epoll</code>, today only Linux 2.6 and over. The
+ implementation of the epoll reactor currently uses the Level Triggered
+ interface, which is basically like poll() but scales much better.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">epollreactor</span>
+<span class="py-src-variable">epollreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h2>GUI Integration Reactors<a name="auto10"/></h2>
+
+ <h3>GTK+<a name="auto11"/></h3><a name="gtk" shape="rect"/>
+
+ <p>Twisted integrates with <a href="http://www.pygtk.org/" shape="rect">PyGTK</a> version
+ 2.0 using the <code>gtk2reactor</code>. An example Twisted application that
+ uses GTK+ can be found
+ in <code class="py-filename">doc/core/examples/pbgtk2.py</code>.</p>
+
+ <p>GTK-2.0 split the event loop out of the GUI toolkit and into a separate
+ module called <q>glib</q>. To run an application using the glib event loop,
+ use the <code>glib2reactor</code>. This will be slightly faster
+ than <code>gtk2reactor</code> (and does not require a working X display),
+ but cannot be used to run GUI applications.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">gtk2reactor</span> <span class="py-src-comment"># for gtk-2.0</span>
+<span class="py-src-variable">gtk2reactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">glib2reactor</span> <span class="py-src-comment"># for non-GUI apps</span>
+<span class="py-src-variable">glib2reactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h3>GTK+ 3.0 and GObject Introspection<a name="auto12"/></h3><a name="gtk3" shape="rect"/>
+
+ <p>Twisted integrates with <a href="http://gtk.org" shape="rect">GTK+ 3</a> and GObject
+ through <a href="http://live.gnome.org/PyGObject" shape="rect">PyGObject's</a>
+ introspection using the <code>gtk3reactor</code>
+ and <code>gireactor</code> reactors.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">gtk3reactor</span>
+<span class="py-src-variable">gtk3reactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">gireactor</span> <span class="py-src-comment"># for non-GUI apps</span>
+<span class="py-src-variable">gireactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <p>GLib 3.0 introduces the concept of <code>GApplication</code>, a class
+ that handles application uniqueness in a cross-platform way and provides
+ its own main loop. Its counterpart <code>GtkApplication</code> also
+ handles application lifetime with respect to open windows. Twisted
+ supports registering these objects with the event loop, which should be
+ done before running the reactor:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">gtk3reactor</span>
+<span class="py-src-variable">gtk3reactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">gi</span>.<span class="py-src-variable">repository</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Gtk</span>
+<span class="py-src-variable">app</span> = <span class="py-src-variable">Gtk</span>.<span class="py-src-variable">Application</span>(...)
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">registerGApplication</span>(<span class="py-src-variable">app</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h3>wxPython<a name="auto13"/></h3><a name="wxpython" shape="rect"/>
+
+ <p>Twisted currently supports two methods of integrating
+ wxPython. Unfortunately, neither method will work on all wxPython
+ platforms (such as GTK2 or Windows). It seems that the only
+ portable way to integrate with wxPython is to run it in a separate
+ thread. One of these methods may be sufficient if your wx app is
+ limited to a single platform.</p>
+
+ <p>As with <a href="#tkinter" shape="rect">Tkinter</a>, the support for integrating
+ Twisted with a <a href="http://www.wxpython.org" shape="rect">wxPython</a>
+ application uses specialized support code rather than a simple reactor.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">wxPython</span>.<span class="py-src-variable">wx</span> <span class="py-src-keyword">import</span> *
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">wxsupport</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">myWxAppInstance</span> = <span class="py-src-variable">wxApp</span>(<span class="py-src-number">0</span>)
+<span class="py-src-variable">wxsupport</span>.<span class="py-src-variable">install</span>(<span class="py-src-variable">myWxAppInstance</span>)
+</pre>
+
+ <p>However, this has issues when running on Windows, so Twisted now
+ comes with alternative wxPython support using a reactor. Using
+ this method is probably better. Initialization is done in two
+ stages. In the first, the reactor is installed:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">wxreactor</span>
+<span class="py-src-variable">wxreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <p>Later, once a <code class="python">wxApp</code> instance has
+ been created, but before <code class="python">reactor.run()</code>
+ is called:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-variable">myWxAppInstance</span> = <span class="py-src-variable">wxApp</span>(<span class="py-src-number">0</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">registerWxApp</span>(<span class="py-src-variable">myWxAppInstance</span>)
+</pre>
+
+ <p>An example Twisted application that uses wxPython can be found
+ in <code class="py-filename">doc/core/examples/wxdemo.py</code>.</p>
+
+ <h3>CoreFoundation<a name="auto14"/></h3><a name="cfreactor" shape="rect"/>
+
+ <p>Twisted integrates with <a href="http://pyobjc.sf.net/" shape="rect">PyObjC</a> version 1.0. Sample applications using Cocoa and Twisted
+ are available in the examples directory under
+ <code>doc/core/examples/threadedselect/Cocoa</code>.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">cfreactor</span>
+<span class="py-src-variable">cfreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h2>Non-Reactor GUI Integration<a name="auto15"/></h2>
+
+ <h3>Tkinter<a name="auto16"/></h3><a name="tkinter" shape="rect"/>
+
+ <p>The support for <a href="http://wiki.python.org/moin/TkInter" shape="rect">Tkinter</a> doesn't use a specialized reactor. Instead, there is
+ some specialized support code:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">Tkinter</span> <span class="py-src-keyword">import</span> *
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">tksupport</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Tk</span>()
+
+<span class="py-src-comment"># Install the Reactor support</span>
+<span class="py-src-variable">tksupport</span>.<span class="py-src-variable">install</span>(<span class="py-src-variable">root</span>)
+
+<span class="py-src-comment"># at this point build Tk app as usual using the root object,</span>
+<span class="py-src-comment"># and start the program with &quot;reactor.run()&quot;, and stop it</span>
+<span class="py-src-comment"># with &quot;reactor.stop()&quot;.</span>
+</pre>
+
+ <h3>PyUI<a name="auto17"/></h3><a name="pyui" shape="rect"/>
+
+ <p>As with <a href="#tkinter" shape="rect">Tkinter</a>, the support for integrating
+ Twisted with a <a href="http://pyui.sourceforge.net" shape="rect">PyUI</a>
+ application uses specialized support code rather than a simple reactor.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pyuisupport</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">pyuisupport</span>.<span class="py-src-variable">install</span>(<span class="py-src-variable">args</span>=(<span class="py-src-number">640</span>, <span class="py-src-number">480</span>), <span class="py-src-variable">kw</span>={<span class="py-src-string">'renderer'</span>: <span class="py-src-string">'gl'</span>})
+</pre>
+
+ <p>An example Twisted application that uses PyUI can be found in <code class="py-filename">doc/core/examples/pyuidemo.py</code>.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/clients.html b/doc/core/howto/clients.html
new file mode 100644
index 0000000..024f32f
--- /dev/null
+++ b/doc/core/howto/clients.html
@@ -0,0 +1,741 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Writing Clients</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Writing Clients</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Protocol</a></li><li><a href="#auto2">Simple, single-use clients</a></li><li><a href="#auto3">ClientFactory</a></li><ul><li><a href="#auto4">Reconnection</a></li></ul><li><a href="#auto5">A Higher-Level Example: ircLogBot</a></li><ul><li><a href="#auto6">Overview of ircLogBot</a></li><li><a href="#auto7">Persistent Data in the Factory</a></li></ul><li><a href="#auto8">Further Reading</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>Twisted is a framework designed to be very flexible, and let you write
+ powerful clients. The cost of this flexibility is a few layers in the way
+ to writing your client. This document covers creating clients that can be
+ used for TCP, SSL and Unix sockets. UDP is covered <a href="udp.html" shape="rect">in
+ a different document</a>.</p>
+
+ <p>At the base, the place where you actually implement the protocol parsing
+ and handling, is the <code>Protocol</code> class. This class will usually be
+ descended
+ from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">twisted.internet.protocol.Protocol</a></code>. Most
+ protocol handlers inherit either from this class or from one of its
+ convenience children. An instance of the protocol class will be instantiated
+ when you connect to the server and will go away when the connection is
+ finished. This means that persistent configuration is not saved in the
+ <code>Protocol</code>.</p>
+
+ <p>The persistent configuration is kept in a <code>Factory</code>
+ class, which usually inherits from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">twisted.internet.protocol.Factory</a></code>
+ (or <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.ClientFactory.html" title="twisted.internet.protocol.ClientFactory">twisted.internet.protocol.ClientFactory</a></code>: see
+ below). The default factory class just instantiates the <code>Protocol</code>
+ and then sets the protocol's <code>factory</code> attribute to point to
+ itself (the factory). This lets the <code>Protocol</code> access, and
+ possibly modify, the persistent configuration.</p>
+
+ <h2>Protocol<a name="auto1"/></h2>
+
+ <p>As mentioned above, this and auxiliary classes and functions are where
+ most of the code is. A Twisted protocol handles data in an asynchronous
+ manner. This means that the protocol never waits for an event, but rather
+ responds to events as they arrive from the network.</p>
+
+ <p>Here is a simple example:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">stdout</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+</pre>
+
+ <p>This is one of the simplest protocols. It just writes whatever it reads
+ from the connection to standard output. There are many events it does not
+ respond to. Here is an example of a <code>Protocol</code> responding to
+ another event:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">WelcomeMessage</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Hello server, I am the client!\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+</pre>
+
+ <p>This protocol connects to the server, sends it a welcome message, and
+ then terminates the connection.</p>
+
+ <p>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.BaseProtocol.connectionMade.html" title="twisted.internet.protocol.BaseProtocol.connectionMade">connectionMade</a></code> event is
+ usually where set up of the <code>Protocol</code> object happens, as well as
+ any initial greetings (as in the
+ <code>WelcomeMessage</code> protocol above). Any tearing down of
+ <code>Protocol</code>-specific objects is done in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Protocol.connectionLost.html" title="twisted.internet.protocol.Protocol.connectionLost">connectionLost</a></code>.</p>
+
+ <h2>Simple, single-use clients<a name="auto2"/></h2>
+
+ <p>In many cases, the protocol only needs to connect to the server once, and
+ the code just wants to get a connected instance of the protocol. In those
+ cases <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.endpoints.html" title="twisted.internet.endpoints">twisted.internet.endpoints</a></code> provides the
+ appropriate API.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">endpoints</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">TCP4ClientEndpoint</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Greeter</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">sendMessage</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;MESSAGE %s\n&quot;</span> % <span class="py-src-variable">msg</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">GreeterFactory</span>(<span class="py-src-parameter">Factory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">Greeter</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">gotProtocol</span>(<span class="py-src-parameter">p</span>):
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">sendMessage</span>(<span class="py-src-string">&quot;Hello&quot;</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">1</span>, <span class="py-src-variable">p</span>.<span class="py-src-variable">sendMessage</span>, <span class="py-src-string">&quot;This is sent in a second&quot;</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">2</span>, <span class="py-src-variable">p</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>)
+
+<span class="py-src-variable">point</span> = <span class="py-src-variable">TCP4ClientEndpoint</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">1234</span>)
+<span class="py-src-variable">d</span> = <span class="py-src-variable">point</span>.<span class="py-src-variable">connect</span>(<span class="py-src-variable">GreeterFactory</span>())
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">gotProtocol</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Regardless of the type of client endpoint, the way to set up a new
+ connection is simply to call the <code>connect</code> method on it and pass
+ in a factory. This means it's easy to change the mechanism you're using to
+ connect, without changing the rest of your program. For example, to run
+ the greeter example over SSL, the only change required is to instantiate an
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.endpoints.SSL4ClientEndpoint.html" title="twisted.internet.endpoints.SSL4ClientEndpoint">SSL4ClientEndpoint</a></code> instead of a
+ <code>TCP4ClientEndpoint</code>. To take advantage of this, functions and
+ methods which initiates a new connection should generally accept an
+ endpoint as an argument and let the caller construct it, rather than taking
+ arguments like 'host' and 'port' and constructing its own before calling
+ <code>connect</code>.</p>
+
+ <p>For more information on different ways you can make outgoing connections
+ to different types of endpoints, as well as parsing strings into endpoints,
+ see <a href="endpoints.html" shape="rect">the documentation for the endpoints
+ API</a>.</p>
+
+ <div class="note"><strong>Note: </strong>If you've used <code>ClientFactory</code> before,
+ make sure you remember that the <code>connect</code> method takes a
+ <code>Factory</code>, not a <code>ClientFactory</code>. Even if you pass a
+ <code>ClientFactory</code> to <code>endpoint.connect</code>, its
+ <code>clientConnectionFailed</code> and <code>clientConnectionLost</code>
+ methods will not be called.</div>
+
+ <p>You may come across code using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.ClientCreator.html" title="twisted.internet.protocol.ClientCreator">ClientCreator</a></code>, an older API which is not as flexible as
+ the endpoint API. Rather than calling <code>connect</code> on an endpoint,
+ such code will look like this:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ClientCreator</span>
+
+...
+
+<span class="py-src-variable">creator</span> = <span class="py-src-variable">ClientCreator</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">Greeter</span>)
+<span class="py-src-variable">d</span> = <span class="py-src-variable">creator</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">1234</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">gotProtocol</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>In general, the endpoint API should be preferred in new code, as it lets
+ the caller select the method of connecting.</p>
+
+ <h2>ClientFactory<a name="auto3"/></h2>
+
+ <p>Still, there's plenty of code out there that uses lower-level APIs, and
+ a few features (such as automatic reconnection) have not been
+ re-implemented with endpoints yet, so in some cases they may be more
+ convenient to use.</p>
+
+ <p>To use the lower-level connection APIs, you will need to call one of the
+ <em>reactor.connect*</em> methods directly. For these cases, you need a
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.ClientFactory.html" title="twisted.internet.protocol.ClientFactory">ClientFactory</a></code>.
+ The <code>ClientFactory</code> is in charge of creating the
+ <code>Protocol</code> and also receives events relating to the connection
+ state. This allows it to do things like reconnect in the event of a
+ connection error. Here is an example of a simple <code>ClientFactory</code>
+ that uses the <code>Echo</code> protocol (above) and also prints what state
+ the connection is in.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>, <span class="py-src-variable">ClientFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">stdout</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClientFactory</span>(<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startedConnecting</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Started to connect.'</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Connected.'</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">Echo</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Lost connection. Reason:'</span>, <span class="py-src-variable">reason</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Connection failed. Reason:'</span>, <span class="py-src-variable">reason</span>
+</pre>
+
+ <p>To connect this <code>EchoClientFactory</code> to a server, you could use
+ this code:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>, <span class="py-src-variable">EchoClientFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Note that <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.ClientFactory.clientConnectionFailed.html" title="twisted.internet.protocol.ClientFactory.clientConnectionFailed">clientConnectionFailed</a></code>
+ is called when a connection could not be established, and that <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.ClientFactory.clientConnectionLost.html" title="twisted.internet.protocol.ClientFactory.clientConnectionLost">clientConnectionLost</a></code>
+ is called when a connection was made and then disconnected.</p>
+
+ <h3>Reconnection<a name="auto4"/></h3>
+
+ <p>Often, the connection of a client will be lost unintentionally due to
+ network problems. One way to reconnect after a disconnection would be to
+ call <code>connector.connect()</code> when the connection is lost:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ClientFactory</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClientFactory</span>(<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">connector</span>.<span class="py-src-variable">connect</span>()
+</pre>
+
+ <p>The connector passed as the first argument is the interface between a
+ connection and a protocol. When the connection fails and the factory
+ receives the <code>clientConnectionLost</code> event, the factory can
+ call <code>connector.connect()</code> to start the connection over again
+ from scratch.</p>
+
+ <p>
+ However, most programs that want this functionality should
+ implement <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.ReconnectingClientFactory.html" title="twisted.internet.protocol.ReconnectingClientFactory">ReconnectingClientFactory</a></code> instead,
+ which tries to reconnect if a connection is lost or fails and which
+ exponentially delays repeated reconnect attempts.
+ </p>
+
+ <p>
+ Here is the <code>Echo</code> protocol implemented with
+ a <code>ReconnectingClientFactory</code>:
+ </p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>, <span class="py-src-variable">ReconnectingClientFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">stdout</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClientFactory</span>(<span class="py-src-parameter">ReconnectingClientFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startedConnecting</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Started to connect.'</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Connected.'</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Resetting reconnection delay'</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">resetDelay</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">Echo</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Lost connection. Reason:'</span>, <span class="py-src-variable">reason</span>
+ <span class="py-src-variable">ReconnectingClientFactory</span>.<span class="py-src-variable">clientConnectionLost</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">connector</span>, <span class="py-src-variable">reason</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Connection failed. Reason:'</span>, <span class="py-src-variable">reason</span>
+ <span class="py-src-variable">ReconnectingClientFactory</span>.<span class="py-src-variable">clientConnectionFailed</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">connector</span>,
+ <span class="py-src-variable">reason</span>)
+</pre>
+
+ <h2>A Higher-Level Example: ircLogBot<a name="auto5"/></h2>
+
+ <h3>Overview of ircLogBot<a name="auto6"/></h3>
+
+ <p>The clients so far have been fairly simple. A more complicated example
+ comes with Twisted Words in the <code>doc/words/examples</code>
+ directory.</p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+</p><span class="py-src-comment"># twisted imports</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+
+<span class="py-src-comment"># system imports</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>, <span class="py-src-variable">sys</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MessageLogger</span>:
+ <span class="py-src-string">&quot;&quot;&quot;
+ An independent logger class (because separation of application
+ and protocol logic is a good thing).
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">file</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">file</span> = <span class="py-src-variable">file</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">log</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Write a message to the file.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">timestamp</span> = <span class="py-src-variable">time</span>.<span class="py-src-variable">strftime</span>(<span class="py-src-string">&quot;[%H:%M:%S]&quot;</span>, <span class="py-src-variable">time</span>.<span class="py-src-variable">localtime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>()))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">file</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">'%s %s\n'</span> % (<span class="py-src-variable">timestamp</span>, <span class="py-src-variable">message</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">file</span>.<span class="py-src-variable">flush</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">close</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">file</span>.<span class="py-src-variable">close</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LogBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+ <span class="py-src-string">&quot;&quot;&quot;A logging IRC bot.&quot;&quot;&quot;</span>
+
+ <span class="py-src-variable">nickname</span> = <span class="py-src-string">&quot;twistedbot&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span> = <span class="py-src-variable">MessageLogger</span>(<span class="py-src-variable">open</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">filename</span>, <span class="py-src-string">&quot;a&quot;</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;[connected at %s]&quot;</span> %
+ <span class="py-src-variable">time</span>.<span class="py-src-variable">asctime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">localtime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>())))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionLost</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">reason</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;[disconnected at %s]&quot;</span> %
+ <span class="py-src-variable">time</span>.<span class="py-src-variable">asctime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">localtime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>())))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">close</span>()
+
+
+ <span class="py-src-comment"># callbacks for events</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">signedOn</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Called when bot has succesfully signed on to server.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">channel</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">joined</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">channel</span>):
+ <span class="py-src-string">&quot;&quot;&quot;This will get called when the bot joins the channel.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;[I have joined %s]&quot;</span> % <span class="py-src-variable">channel</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-string">&quot;&quot;&quot;This will get called when the bot receives a message.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>, <span class="py-src-number">1</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;&lt;%s&gt; %s&quot;</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">msg</span>))
+
+ <span class="py-src-comment"># Check to see if they're sending me a private message</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">channel</span> == <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>:
+ <span class="py-src-variable">msg</span> = <span class="py-src-string">&quot;It isn't nice to whisper! Play nice with the group.&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">msg</span>)
+ <span class="py-src-keyword">return</span>
+
+ <span class="py-src-comment"># Otherwise check to see if it is a message directed at me</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">msg</span>.<span class="py-src-variable">startswith</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> + <span class="py-src-string">&quot;:&quot;</span>):
+ <span class="py-src-variable">msg</span> = <span class="py-src-string">&quot;%s: I am a log bot&quot;</span> % <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">channel</span>, <span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;&lt;%s&gt; %s&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>, <span class="py-src-variable">msg</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">action</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-string">&quot;&quot;&quot;This will get called when the bot sees someone do an action.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>, <span class="py-src-number">1</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;* %s %s&quot;</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">msg</span>))
+
+ <span class="py-src-comment"># irc callbacks</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">irc_NICK</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">prefix</span>, <span class="py-src-parameter">params</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Called when an IRC user changes their nickname.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">old_nick</span> = <span class="py-src-variable">prefix</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">new_nick</span> = <span class="py-src-variable">params</span>[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;%s is now known as %s&quot;</span> % (<span class="py-src-variable">old_nick</span>, <span class="py-src-variable">new_nick</span>))
+
+
+ <span class="py-src-comment"># For fun, override the method that determines how a nickname is changed on</span>
+ <span class="py-src-comment"># collisions. The default method appends an underscore.</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">alterCollidedNick</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">nickname</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Generate an altered version of a nickname that caused a collision in an
+ effort to create an unused related name for subsequent registration.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">nickname</span> + <span class="py-src-string">'^'</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LogBotFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-string">&quot;&quot;&quot;A factory for LogBots.
+
+ A new protocol instance will be created each time we connect to the server.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">channel</span> = <span class="py-src-variable">channel</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">LogBot</span>()
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">factory</span> = <span class="py-src-variable">self</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">p</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-string">&quot;&quot;&quot;If we get disconnected, reconnect to server.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">connector</span>.<span class="py-src-variable">connect</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;connection failed:&quot;</span>, <span class="py-src-variable">reason</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-comment"># initialize logging</span>
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">startLogging</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">stdout</span>)
+
+ <span class="py-src-comment"># create factory protocol and application</span>
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">LogBotFactory</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">1</span>], <span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">2</span>])
+
+ <span class="py-src-comment"># connect factory to this host and port</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;irc.freenode.net&quot;</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">f</span>)
+
+ <span class="py-src-comment"># run bot</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="../../words/examples/ircLogBot.py"><span class="filename">../../words/examples/ircLogBot.py</span></a></div></div>
+
+ <p><code>ircLogBot.py</code> connects to an IRC server, joins a channel, and
+ logs all traffic on it to a file. It demonstrates some of the
+ connection-level logic of reconnecting on a lost connection, as well as
+ storing persistent data in the <code>Factory</code>.</p>
+
+ <h3>Persistent Data in the Factory<a name="auto7"/></h3>
+
+ <p>Since the <code>Protocol</code> instance is recreated each time the
+ connection is made, the client needs some way to keep track of data that
+ should be persisted. In the case of the logging bot, it needs to know which
+ channel it is logging, and where to log it.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LogBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span> = <span class="py-src-variable">MessageLogger</span>(<span class="py-src-variable">open</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">filename</span>, <span class="py-src-string">&quot;a&quot;</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;[connected at %s]&quot;</span> %
+ <span class="py-src-variable">time</span>.<span class="py-src-variable">asctime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">localtime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>())))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">signedOn</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">channel</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LogBotFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">channel</span> = <span class="py-src-variable">channel</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">LogBot</span>()
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">factory</span> = <span class="py-src-variable">self</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">p</span>
+</pre>
+
+ <p>When the protocol is created, it gets a reference to the factory as
+ <code>self.factory</code>. It can then access attributes of the factory in
+ its logic. In the case of <code>LogBot</code>, it opens the file and
+ connects to the channel stored in the factory.</p>
+
+ <p>Factories have a default implementation of <code>buildProtocol</code>
+ that does the same thing the example above does, using
+ the <code>protocol</code> attribute of the factory to create the protocol
+ instance. In the example above, the factory could be rewritten to look
+ like this:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">LogBotFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">LogBot</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">channel</span> = <span class="py-src-variable">channel</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+</pre>
+
+ <h2>Further Reading<a name="auto8"/></h2>
+
+ <p>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">Protocol</a></code>
+ class used throughout this document is a base implementation
+ of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProtocol.html" title="twisted.internet.interfaces.IProtocol">IProtocol</a></code>
+ used in most Twisted applications for convenience. To learn about the
+ complete <code>IProtocol</code> interface, see the API documentation for
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProtocol.html" title="twisted.internet.interfaces.IProtocol">IProtocol</a></code>.</p>
+
+ <p>The <code>transport</code> attribute used in some examples in this
+ document provides the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.ITCPTransport.html" title="twisted.internet.interfaces.ITCPTransport">ITCPTransport</a></code> interface. To learn
+ about the complete interface, see the API documentation
+ for <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.ITCPTransport.html" title="twisted.internet.interfaces.ITCPTransport">ITCPTransport</a></code>.</p>
+
+ <p>Interface classes are a way of specifying what methods and attributes an
+ object has and how they behave. See the <a href="components.html" shape="rect">
+ Components: Interfaces and Adapters</a> document for more information on
+ using interfaces in Twisted.</p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/components.html b/doc/core/howto/components.html
new file mode 100644
index 0000000..b52e9e7
--- /dev/null
+++ b/doc/core/howto/components.html
@@ -0,0 +1,603 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Components: Interfaces and Adapters</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Components: Interfaces and Adapters</h1>
+ <div class="toc"><ol><li><a href="#auto0">Interfaces and Components in Twisted code</a></li><ul><li><a href="#auto1">Components and Inheritance</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<p>Object oriented programming languages allow programmers to reuse portions of
+existing code by creating new <q>classes</q> of objects which subclass another
+class. When a class subclasses another, it is said to <em>inherit</em> all of its
+behaviour. The subclass can then <q>override</q> and <q>extend</q> the behavior
+provided to it by the superclass. Inheritance is very useful in many situations,
+but because it is so convenient to use, often becomes abused in large software
+systems, especially when multiple inheritance is involved. One solution is to
+use <em>delegation</em> instead of <q>inheritance</q> where appropriate.
+Delegation is simply the act of asking <em>another</em> object to perform a task
+for an object. To support this design pattern, which is often referred to as
+the <em>components</em> pattern because it involves many small interacting
+components, <em>interfaces</em> and <em>adapters</em> were created by the Zope
+3 team.</p>
+
+<p><q>Interfaces</q> are simply markers which objects can use to say <q>I
+implement this interface</q>. Other objects may then make requests like
+<q>Please give me an object which implements interface X for object type Y</q>.
+Objects which implement an interface for another object type are called
+<q>adapters</q>.</p>
+
+<p>The superclass-subclass relationship is said to be an <em>is-a</em> relationship.
+When designing object hierarchies, object modellers use subclassing when they
+can say that the subclass <em>is</em> the same class as the superclass. For
+example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Shape</span>:
+ <span class="py-src-variable">sideLength</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getSideLength</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setSideLength</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">sideLength</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span> = <span class="py-src-variable">sideLength</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">area</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>, <span class="py-src-string">&quot;Subclasses must implement area&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Triangle</span>(<span class="py-src-parameter">Shape</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">area</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> (<span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span> * <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span>) / <span class="py-src-number">2</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Square</span>(<span class="py-src-parameter">Shape</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">area</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span> * <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span>
+</pre>
+
+<p>In the above example, a Triangle <em>is-a</em> Shape, so it subclasses Shape,
+and a Square <em>is-a</em> Shape, so it also subclasses Shape.</p>
+
+<p>However, subclassing can get complicated, especially when Multiple
+Inheritance enters the picture. Multiple Inheritance allows a class to inherit
+from more than one base class. Software which relies heavily on inheritance
+often ends up having both very wide and very deep inheritance trees, meaning
+that one class inherits from many superclasses spread throughout the system.
+Since subclassing with Multiple Inheritance means <em>implementation
+inheritance</em>, locating a method's actual implementation and ensuring the
+correct method is actually being invoked becomes a challenge. For example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Area</span>:
+ <span class="py-src-variable">sideLength</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getSideLength</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setSideLength</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">sideLength</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span> = <span class="py-src-variable">sideLength</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">area</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>, <span class="py-src-string">&quot;Subclasses must implement area&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Color</span>:
+ <span class="py-src-variable">color</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setColor</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">color</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">color</span> = <span class="py-src-variable">color</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getColor</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">color</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Square</span>(<span class="py-src-parameter">Area</span>, <span class="py-src-parameter">Color</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">area</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span> * <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span>
+</pre>
+
+<p>The reason programmers like using implementation inheritance is because it
+makes code easier to read since the implementation details of Area are in a
+separate place than the implementation details of Color. This is nice, because
+conceivably an object could have a color but not an area, or an area but not a
+color. The problem, though, is that Square is not really an Area or a Color, but
+has an area and color. Thus, we should really be using another object oriented
+technique called <em>composition</em>, which relies on delegation rather than
+inheritance to break code into small reusable chunks. Let us continue with the
+Multiple Inheritance example, though, because it is often used in practice.</p>
+
+<p>What if both the Color and the Area base class defined the same
+method, perhaps <code>calculate</code>? Where would the implementation
+come from? The implementation that is located
+for <code>Square().calculate()</code> depends on the method resolution
+order, or MRO, and can change when programmers change seemingly
+unrelated things by refactoring classes in other parts of the system,
+causing obscure bugs. Our first thought might be to change the
+calculate method name to avoid name clashes, to
+perhaps <code>calculateArea</code> and <code>calculateColor</code>.
+While explicit, this change could potentially require a large number
+of changes throughout a system, and is error-prone, especially when
+attempting to integrate two systems which you didn't write.</p>
+
+<p>Let's imagine another example. We have an electric appliance, say a hair
+dryer. The hair dryer is American voltage. We have two electric sockets, one of
+them an American 120 Volt socket, and one of them a United Kingdom 240 Volt socket. If
+we plug the hair dryer into the 240 Volt socket, it is going to expect 120 Volt
+current and errors will result. Going back and changing the hair dryer to
+support both <code>plug120Volt</code> and <code>plug240Volt</code> methods would
+be tedious, and what if we decided we needed to plug the hair dryer into yet
+another type of socket? For example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">HairDryer</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">plug</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">socket</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">socket</span>.<span class="py-src-variable">voltage</span>() == <span class="py-src-number">120</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I was plugged in properly and am operating.&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I was plugged in improperly and &quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;now you have no hair dryer any more.&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AmericanSocket</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-number">120</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UKSocket</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-number">240</span>
+</pre>
+
+<p>Given these classes, the following operations can be performed:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; hd = HairDryer()
+&gt;&gt;&gt; am = AmericanSocket()
+&gt;&gt;&gt; hd.plug(am)
+I was plugged in properly and am operating.
+&gt;&gt;&gt; uk = UKSocket()
+&gt;&gt;&gt; hd.plug(uk)
+I was plugged in improperly and
+now you have no hair dryer any more.
+</pre>
+
+<p>We are going to attempt to solve this problem by writing an Adapter for
+the <code>UKSocket</code> which converts the voltage for use with an American
+hair dryer. An Adapter is a class which is constructed with one and only one
+argument, the <q>adaptee</q> or <q>original</q> object. In this example, we
+will show all code involved for clarity:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">AdaptToAmericanSocket</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">original</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span> = <span class="py-src-variable">original</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span>.<span class="py-src-variable">voltage</span>() / <span class="py-src-number">2</span>
+</pre>
+
+<p>Now, we can use it as so:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; hd = HairDryer()
+&gt;&gt;&gt; uk = UKSocket()
+&gt;&gt;&gt; adapted = AdaptToAmericanSocket(uk)
+&gt;&gt;&gt; hd.plug(adapted)
+I was plugged in properly and am operating.
+</pre>
+
+<p>So, as you can see, an adapter can 'override' the original implementation. It
+can also 'extend' the interface of the original object by providing methods the
+original object did not have. Note that an Adapter must explicitly delegate any
+method calls it does not wish to modify to the original, otherwise the Adapter
+cannot be used in places where the original is expected. Usually this is not a
+problem, as an Adapter is created to conform an object to a particular interface
+and then discarded.</p>
+
+<h2>Interfaces and Components in Twisted code<a name="auto0"/></h2>
+
+<p>Adapters are a useful way of using multiple classes to factor code into
+discrete chunks. However, they are not very interesting without some more
+infrastructure. If each piece of code which wished to use an adapted object had
+to explicitly construct the adapter itself, the coupling between components
+would be too tight. We would like to achieve <q>loose coupling</q>, and this is
+where <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.components.html" title="twisted.python.components">twisted.python.components</a></code> comes in.</p>
+
+<p>First, we need to discuss Interfaces in more detail. As we mentioned
+earlier, an Interface is nothing more than a class which is used as a marker.
+Interfaces should be subclasses of <code>zope.interface.Interface</code>, and
+have a very odd look to python programmers not used to them:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IAmericanSocket</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return the voltage produced by this socket object, as an integer.
+ &quot;&quot;&quot;</span>
+</pre>
+
+<p>Notice how it looks just like a regular class definition, other than
+inheriting from <code>Interface</code>? However, the method definitions inside
+the class block do not have any method body! Since Python does not have any
+native language-level support for Interfaces like Java does, this is what
+distinguishes an Interface definition from a Class.</p>
+
+<p>Now that we have a defined Interface, we can talk about objects using terms
+like this: <q>The <code>AmericanSocket</code> class implements the <code>IAmericanSocket</code> interface</q> and <q>Please give me an object which
+adapts <code>UKSocket</code> to the <code>IAmericanSocket</code>
+interface</q>. We can make <em>declarations</em> about what interfaces a certain
+class implements, and we can request adapters which implement a certain
+interface for a specific class.</p>
+
+<p>Let's look at how we declare that a class implements an interface:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AmericanSocket</span>:
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IAmericanSocket</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-number">120</span>
+</pre>
+
+<p>So, to declare that a class implements an interface, we simply
+call <code>zope.interface.implements</code> at the class level.</p>
+
+<p>Now, let's say we want to rewrite
+the <code>AdaptToAmericanSocket</code> class as a real adapter. In
+this case we also specify it as
+implementing <code>IAmericanSocket</code>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AdaptToAmericanSocket</span>:
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IAmericanSocket</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">original</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Pass the original UKSocket object as original
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span> = <span class="py-src-variable">original</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span>.<span class="py-src-variable">voltage</span>() / <span class="py-src-number">2</span>
+</pre>
+
+<p>Notice how we placed the implements declaration on this adapter class. So
+far, we have not achieved anything by using components other than requiring us
+to type more. In order for components to be useful, we must use the
+<em>component registry</em>. Since <code>AdaptToAmericanSocket</code>
+implements
+<code>IAmericanSocket</code> and regulates the voltage of a
+<code>UKSocket</code> object, we can register
+<code>AdaptToAmericanSocket</code> as an <code>IAmericanSocket</code> adapter
+for the <code>UKSocket</code> class. It is easier to see how this is
+done in code than to describe it:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IAmericanSocket</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return the voltage produced by this socket object, as an integer.
+ &quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AmericanSocket</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IAmericanSocket</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-number">120</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UKSocket</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-number">240</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AdaptToAmericanSocket</span>:
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IAmericanSocket</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">original</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span> = <span class="py-src-variable">original</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span>.<span class="py-src-variable">voltage</span>() / <span class="py-src-number">2</span>
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(
+ <span class="py-src-variable">AdaptToAmericanSocket</span>,
+ <span class="py-src-variable">UKSocket</span>,
+ <span class="py-src-variable">IAmericanSocket</span>)
+</pre>
+
+<p>Now, if we run this script in the interactive interpreter, we can discover a
+little more about how to use components. The first thing we can do is discover
+whether an object implements an interface or not:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; IAmericanSocket.implementedBy(AmericanSocket)
+True
+&gt;&gt;&gt; IAmericanSocket.implementedBy(UKSocket)
+False
+&gt;&gt;&gt; am = AmericanSocket()
+&gt;&gt;&gt; uk = UKSocket()
+&gt;&gt;&gt; IAmericanSocket.providedBy(am)
+True
+&gt;&gt;&gt; IAmericanSocket.providedBy(uk)
+False
+</pre>
+
+<p>As you can see, the <code>AmericanSocket</code> instance claims to
+implement <code>IAmericanSocket</code>, but the <code>UKSocket</code>
+does not. If we wanted to use the <code>HairDryer</code> with the <code>AmericanSocket</code>, we could know that it would be safe to do so by
+checking whether it implements <code>IAmericanSocket</code>. However, if we
+decide we want to use <code>HairDryer</code> with a <code>UKSocket</code>
+instance, we must <em>adapt</em> it to <code>IAmericanSocket</code> before
+doing so. We use the interface object to do this:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; IAmericanSocket(uk)
+&lt;__main__.AdaptToAmericanSocket instance at 0x1a5120&gt;
+</pre>
+
+<p>When calling an interface with an object as an argument, the interface
+looks in the adapter registry for an adapter which implements the interface for
+the given instance's class. If it finds one, it constructs an instance of the
+Adapter class, passing the constructor the original instance, and returns it.
+Now the <code>HairDryer</code> can safely be used with the adapted <code>UKSocket</code>. But what happens if we attempt to adapt an object
+which already implements <code>IAmericanSocket</code>? We simply get back the
+original instance:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; IAmericanSocket(am)
+&lt;__main__.AmericanSocket instance at 0x36bff0&gt;
+</pre>
+
+<p>So, we could write a new <q>smart</q> <code>HairDryer</code> which
+automatically looked up an adapter for the socket you tried to plug it into:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">HairDryer</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">plug</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">socket</span>):
+ <span class="py-src-variable">adapted</span> = <span class="py-src-variable">IAmericanSocket</span>(<span class="py-src-variable">socket</span>)
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">adapted</span>.<span class="py-src-variable">voltage</span>() == <span class="py-src-number">120</span>, <span class="py-src-string">&quot;BOOM&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I was plugged in properly and am operating&quot;</span>
+</pre>
+
+<p>Now, if we create an instance of our new <q>smart</q> <code>HairDryer</code>
+and attempt to plug it in to various sockets, the <code>HairDryer</code> will
+adapt itself automatically depending on the type of socket it is plugged in
+to:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; am = AmericanSocket()
+&gt;&gt;&gt; uk = UKSocket()
+&gt;&gt;&gt; hd = HairDryer()
+&gt;&gt;&gt; hd.plug(am)
+I was plugged in properly and am operating
+&gt;&gt;&gt; hd.plug(uk)
+I was plugged in properly and am operating
+</pre>
+
+<p>Voila; the magic of components.</p>
+
+<h3>Components and Inheritance<a name="auto1"/></h3>
+
+<p>If you inherit from a class which implements some interface, and your new
+subclass declares that it implements another interface, the implements will be
+inherited by default.</p>
+
+<p>For example, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code> is a class
+which implements <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.IPBRoot.html" title="twisted.spread.pb.IPBRoot">IPBRoot</a></code>. This interface indicates that an
+object has remotely-invokable methods and can be used as the initial object
+served by a new Broker instance. It has an <code>implements</code> setting
+like:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Root</span>(<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPBRoot</span>)
+</pre>
+
+<p>Suppose you have your own class which implements your
+<code>IMyInterface</code> interface:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>, <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMyInterface</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyThing</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IMyInterface</span>)
+</pre>
+
+<p>Now if you want to make this class inherit from <code>pb.Root</code>,
+the interfaces code will automatically determine that it also implements
+<code>IPBRoot</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>, <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMyInterface</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyThing</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IMyInterface</span>)
+</pre>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; from twisted.spread.flavors import IPBRoot
+&gt;&gt;&gt; IPBRoot.implementedBy(MyThing)
+True
+</pre>
+
+<p>If you want <code>MyThing</code> to inherit from <code>pb.Root</code> but <em>not</em> implement <code>IPBRoot</code> like <code>pb.Root</code> does,
+use <code>implementOnly</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implementsOnly</span>, <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMyInterface</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyThing</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-variable">implementsOnly</span>(<span class="py-src-variable">IMyInterface</span>)
+</pre>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; from twisted.spread.pb import IPBRoot
+&gt;&gt;&gt; IPBRoot.implementedBy(MyThing)
+False
+</pre>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/constants.html b/doc/core/howto/constants.html
new file mode 100644
index 0000000..ede0af0
--- /dev/null
+++ b/doc/core/howto/constants.html
@@ -0,0 +1,456 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Symbolic Constants</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Symbolic Constants</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Constant Names</a></li><li><a href="#auto2">Constants With Values</a></li><li><a href="#auto3">Constants As Flags</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>It is often useful to define names which will be treated as
+ constants. <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.constants.html" title="twisted.python.constants">twisted.python.constants</a></code> provides APIs
+ for defining such symbolic constants with minimal overhead and some useful
+ features beyond those afforded by the common Python idioms for this task.</p>
+
+ <p>This document will explain how to use these APIs and what circumstances
+ they might be helpful in.</p>
+
+ <h2>Constant Names<a name="auto1"/></h2>
+
+ <p>Constants which have no value apart from their name and identity can be
+ defined by subclassing <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.constants.Names.html" title="twisted.python.constants.Names">Names</a></code>.
+ Consider this example, in which some HTTP request method constants are defined.</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">constants</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NamedConstant</span>, <span class="py-src-variable">Names</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">METHOD</span>(<span class="py-src-parameter">Names</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Constants representing various HTTP request methods.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">GET</span> = <span class="py-src-variable">NamedConstant</span>()
+ <span class="py-src-variable">PUT</span> = <span class="py-src-variable">NamedConstant</span>()
+ <span class="py-src-variable">POST</span> = <span class="py-src-variable">NamedConstant</span>()
+ <span class="py-src-variable">DELETE</span> = <span class="py-src-variable">NamedConstant</span>()
+</pre>
+
+ <p>Only direct subclasses of <code>Names</code> are supported (i.e., you
+ cannot subclass <code>METHOD</code> to add new constants the collection).</p>
+
+ <p>Given this definition, constants can be looked up by name using attribute
+ access on the <code>METHOD</code> object:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; METHOD.GET
+&lt;METHOD=GET&gt;
+&gt;&gt;&gt; METHOD.PUT
+&lt;METHOD=PUT&gt;
+&gt;&gt;&gt;
+ </pre>
+
+ <p>If it's necessary to look up constants based on user input of some sort, a
+ safe way to do it is using <code>lookupByName</code>:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; METHOD.lookupByName('GET')
+&lt;METHOD=GET&gt;
+&gt;&gt;&gt; METHOD.lookupByName('__doc__')
+Traceback (most recent call last):
+ File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;
+ File &quot;twisted/python/constants.py&quot;, line 145, in lookupByName
+ raise ValueError(name)
+ValueError: __doc__
+&gt;&gt;&gt;
+ </pre>
+
+ <p>As demonstrated, it is safe because any name not associated with a constant
+ (even those special names initialized by Python itself) will result
+ in <code>ValueError</code> being raised, not some other object not intended to
+ be used the way the constants are used.</p>
+
+ <p>The constants can also be enumerated using the <code>iterconstants</code>
+ method.</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; list(METHOD.iterconstants())
+[&lt;METHOD=GET&gt;, &lt;METHOD=PUT&gt;, &lt;METHOD=POST&gt;, &lt;METHOD=DELETE&gt;]
+&gt;&gt;&gt;
+ </pre>
+
+ <p>And constants can also be compared, either for equality or identity:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; METHOD.GET is METHOD.GET
+True
+&gt;&gt;&gt; METHOD.GET == METHOD.GET
+True
+&gt;&gt;&gt; METHOD.GET is METHOD.PUT
+False
+&gt;&gt;&gt; METHOD.GET == METHOD.PUT
+False
+&gt;&gt;&gt;
+ </pre>
+
+ <p>Custom functionality can also be associated with constants defined this
+ way. A subclass of <code>Names</code> may define class methods to implement
+ such functionality. Consider this redefinition of <code>METHOD</code>:</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">constants</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NamedConstant</span>, <span class="py-src-variable">Names</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">METHOD</span>(<span class="py-src-parameter">Names</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Constants representing various HTTP request methods.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">GET</span> = <span class="py-src-variable">NamedConstant</span>()
+ <span class="py-src-variable">PUT</span> = <span class="py-src-variable">NamedConstant</span>()
+ <span class="py-src-variable">POST</span> = <span class="py-src-variable">NamedConstant</span>()
+ <span class="py-src-variable">DELETE</span> = <span class="py-src-variable">NamedConstant</span>()
+
+ @<span class="py-src-variable">classmethod</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">isIdempotent</span>(<span class="py-src-parameter">cls</span>, <span class="py-src-parameter">method</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return True if the given method is side-effect free, False otherwise.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">method</span> <span class="py-src-keyword">is</span> <span class="py-src-variable">cls</span>.<span class="py-src-variable">GET</span>
+</pre>
+
+ <p>This functionality can be used as any class methods are used:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; METHOD.isIdempotent(METHOD.GET)
+True
+&gt;&gt;&gt; METHOD.isIdempotent(METHOD.POST)
+False
+&gt;&gt;&gt;
+ </pre>
+
+ <h2>Constants With Values<a name="auto2"/></h2>
+
+ <p>Constants with a particular associated value are supported by
+ the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.constants.Values.html" title="twisted.python.constants.Values">Values</a></code> base
+ class. Consider this example, in which some HTTP status code constants are
+ defined.
+ </p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">constants</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ValueConstant</span>, <span class="py-src-variable">Values</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">STATUS</span>(<span class="py-src-parameter">Values</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Constants representing various HTTP status codes.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">OK</span> = <span class="py-src-variable">ValueConstant</span>(<span class="py-src-string">&quot;200&quot;</span>)
+ <span class="py-src-variable">FOUND</span> = <span class="py-src-variable">ValueConstant</span>(<span class="py-src-string">&quot;302&quot;</span>)
+ <span class="py-src-variable">NOT_FOUND</span> = <span class="py-src-variable">ValueConstant</span>(<span class="py-src-string">&quot;404&quot;</span>)
+</pre>
+
+ <p>As with <code>Names</code>, constants are accessed as attributes of the
+ class object:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; STATUS.OK
+&lt;STATUS=OK&gt;
+&gt;&gt;&gt; STATUS.FOUND
+&lt;STATUS=FOUND&gt;
+&gt;&gt;&gt;
+ </pre>
+
+ <p>Additionally, the values of the constants can be accessed using
+ the <code>value</code> attribute of one these objects:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; STATUS.OK.value
+'200'
+&gt;&gt;&gt;
+ </pre>
+
+ <p>And as with <code>Names</code>, constants can be looked up by name:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; STATUS.lookupByName('NOT_FOUND')
+&lt;STATUS=NOT_FOUND&gt;
+&gt;&gt;&gt;
+ </pre>
+
+ <p>Constants on a <code>Values</code> subclass can also be looked up by
+ value:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; STATUS.lookupByValue('404')
+&lt;STATUS=NOT_FOUND&gt;
+&gt;&gt;&gt; STATUS.lookupByValue('500')
+Traceback (most recent call last):
+ File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;
+ File &quot;twisted/python/constants.py&quot;, line 244, in lookupByValue
+ raise ValueError(value)
+ValueError: 500
+&gt;&gt;&gt;
+ </pre>
+
+ <p>Multiple constants may have the same value. If they do,
+ <code>lookupByValue</code> will find the one which is defined first.</p>
+
+ <p>Iteration is also supported:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; list(STATUS.iterconstants())
+[&lt;STATUS=OK&gt;, &lt;STATUS=FOUND&gt;, &lt;STATUS=NOT_FOUND&gt;]
+&gt;&gt;&gt;
+ </pre>
+
+ <p>And constants can be compared for equality and identity:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; STATUS.OK == STATUS.OK
+True
+&gt;&gt;&gt; STATUS.OK is STATUS.OK
+True
+&gt;&gt;&gt; STATUS.OK == STATUS.OK
+True
+&gt;&gt;&gt; STATUS.OK is STATUS.NOT_FOUND
+False
+&gt;&gt;&gt; STATUS.OK == STATUS.NOT_FOUND
+False
+&gt;&gt;&gt;
+ </pre>
+
+ <p>And, as with <code>Names</code>, a subclass of <code>Values</code> can
+ define methods:</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">constants</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ValueConstant</span>, <span class="py-src-variable">Values</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">STATUS</span>(<span class="py-src-parameter">Values</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Constants representing various HTTP status codes.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">OK</span> = <span class="py-src-variable">ValueConstant</span>(<span class="py-src-string">&quot;200&quot;</span>)
+ <span class="py-src-variable">NO_CONTENT</span> = <span class="py-src-variable">ValueConstant</span>(<span class="py-src-string">&quot;204&quot;</span>)
+ <span class="py-src-variable">NOT_MODIFIED</span> = <span class="py-src-variable">ValueConstant</span>(<span class="py-src-string">&quot;304&quot;</span>)
+ <span class="py-src-variable">NOT_FOUND</span> = <span class="py-src-variable">ValueConstant</span>(<span class="py-src-string">&quot;404&quot;</span>)
+
+ @<span class="py-src-variable">classmethod</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">hasBody</span>(<span class="py-src-parameter">cls</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return True if the given status is associated with a response body,
+ False otherwise.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">status</span> <span class="py-src-keyword">in</span> (<span class="py-src-variable">cls</span>.<span class="py-src-variable">NO_CONTENT</span>, <span class="py-src-variable">cls</span>.<span class="py-src-variable">NOT_MODIFIED</span>)
+</pre>
+
+ <p>This functionality can be used as any class methods are used:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; STATUS.hasBody(STATUS.OK)
+True
+&gt;&gt;&gt; STATUS.hasBody(STATUS.NO_CONTENT)
+False
+&gt;&gt;&gt;
+ </pre>
+
+ <h2>Constants As Flags<a name="auto3"/></h2>
+
+ <p>Integers are often used as a simple set for constants. The values for
+ these constants are assigned as powers of two so that bits in the integer can
+ be set to represent them. Individual bits are often called <em>flags</em>.
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.constants.Flags.html" title="twisted.python.constants.Flags">Flags</a></code> supports this
+ use-case, including allowing constants with particular bits to be set, for
+ interoperability with other tools.</p>
+
+ <p>POSIX filesystem access control is traditionally done using a bitvector
+ defining which users and groups may perform which operations on a file. This
+ state might be represented using <code>Flags</code> as follows:</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">constants</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FlagConstant</span>, <span class="py-src-variable">Flags</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Permission</span>(<span class="py-src-parameter">Flags</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Constants representing user, group, and other access bits for reading,
+ writing, and execution.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">OTHER_EXECUTE</span> = <span class="py-src-variable">FlagConstant</span>()
+ <span class="py-src-variable">OTHER_WRITE</span> = <span class="py-src-variable">FlagConstant</span>()
+ <span class="py-src-variable">OTHER_READ</span> = <span class="py-src-variable">FlagConstant</span>()
+ <span class="py-src-variable">GROUP_EXECUTE</span> = <span class="py-src-variable">FlagConstant</span>()
+ <span class="py-src-variable">GROUP_WRITE</span> = <span class="py-src-variable">FlagConstant</span>()
+ <span class="py-src-variable">GROUP_READ</span> = <span class="py-src-variable">FlagConstant</span>()
+ <span class="py-src-variable">USER_EXECUTE</span> = <span class="py-src-variable">FlagConstant</span>()
+ <span class="py-src-variable">USER_WRITE</span> = <span class="py-src-variable">FlagConstant</span>()
+ <span class="py-src-variable">USER_READ</span> = <span class="py-src-variable">FlagConstant</span>()
+</pre>
+
+ <p>
+ As for the previous types of constants, these can be accessed as attributes
+ of the class object:
+ </p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; Permission.USER_READ
+&lt;Permission=USER_READ&gt;
+&gt;&gt;&gt; Permission.USER_WRITE
+&lt;Permission=USER_WRITE&gt;
+&gt;&gt;&gt; Permission.USER_EXECUTE
+&lt;Permission=USER_EXECUTE&gt;
+&gt;&gt;&gt;
+ </pre>
+
+ <p>These constant objects also have a <code>value</code> attribute giving
+ their integer value:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; Permission.USER_READ.value
+256
+&gt;&gt;&gt;
+ </pre>
+
+ <p>And these constants can be looked up by name or value:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; Permission.lookupByName('USER_READ') is Permission.USER_READ
+True
+&gt;&gt;&gt; Permission.lookupByValue(256) is Permission.USER_READ
+True
+&gt;&gt;&gt;
+ </pre>
+
+ <p>Constants can also be combined using the logical operators <code>&amp;</code>
+ (<em>and</em>), <code>|</code> (<em>or</em>), and <code>^</code>
+ (<em>exclusive or</em>).
+ </p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; Permission.USER_READ | Permission.USER_WRITE
+&lt;Permission={USER_READ,USER_WRITE}&gt;
+&gt;&gt;&gt; (Permission.USER_READ | Permission.USER_WRITE) &amp; Permission.USER_WRITE
+&lt;Permission=USER_WRITE&gt;
+&gt;&gt;&gt; (Permission.USER_READ | Permission.USER_WRITE) ^ Permission.USER_WRITE
+&lt;Permission=USER_READ&gt;
+&gt;&gt;&gt;
+ </pre>
+
+ <p>The unary operator <code>~</code> (<em>not</em>) is also defined:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; ~Permission.USER_READ
+&lt;Permission={GROUP_EXECUTE,GROUP_READ,GROUP_WRITE,OTHER_EXECUTE,OTHER_READ,OTHER_WRITE,USER_EXECUTE,USER_WRITE}&gt;
+&gt;&gt;&gt;
+ </pre>
+
+ <p>Constants created using these operators also have a <code>value</code>
+ attribute.</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; (~Permission.USER_WRITE).value
+383
+&gt;&gt;&gt;
+ </pre>
+
+ <p>
+ Note the care taken to ensure the <code>~</code> operator is applied first
+ and the <code>value</code>attribute is looked up second.
+ </p>
+
+ <p>A <code>Flags</code> subclass can also define methods, just as
+ a <code>Names</code> or <code>Values</code> subclass may. For example,
+ <code>Permission</code> might benefit from a method to format a flag as a
+ string in the traditional style. Consider this addition to that class:</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">filepath</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">constants</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FlagConstant</span>, <span class="py-src-variable">Flags</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Permission</span>(<span class="py-src-parameter">Flags</span>):
+ ...
+
+ @<span class="py-src-variable">classmethod</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">format</span>(<span class="py-src-parameter">cls</span>, <span class="py-src-parameter">permissions</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Format permissions flags in the traditional 'rwxr-xr-x' style.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">filepath</span>.<span class="py-src-variable">Permissions</span>(<span class="py-src-variable">permissions</span>.<span class="py-src-variable">value</span>).<span class="py-src-variable">shorthand</span>()
+</pre>
+
+ <p>Use this like any other class method:</p>
+
+ <pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; Permission.format(Permission.USER_READ | Permission.USER_WRITE | Permission.GROUP_READ | Permission.OTHER_READ)
+'rw-r--r--'
+&gt;&gt;&gt;
+ </pre>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/cred.html b/doc/core/howto/cred.html
new file mode 100644
index 0000000..44b4822
--- /dev/null
+++ b/doc/core/howto/cred.html
@@ -0,0 +1,566 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Cred: Pluggable Authentication</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Cred: Pluggable Authentication</h1>
+ <div class="toc"><ol><li><a href="#auto0">Goals</a></li><li><a href="#auto1">Cred objects</a></li><ul><li><a href="#auto2">The Portal</a></li><li><a href="#auto3">The CredentialChecker</a></li><li><a href="#auto4">The Credentials</a></li><li><a href="#auto5">The Realm</a></li><li><a href="#auto6">The Avatar</a></li><li><a href="#auto7">The Mind</a></li></ul><li><a href="#auto8">Responsibilities</a></li><ul><li><a href="#auto9">Server protocol implementation</a></li><li><a href="#auto10">Application implementation</a></li><li><a href="#auto11">Deployment</a></li></ul><li><a href="#auto12">Cred plugins</a></li><ul><li><a href="#auto13">Authentication with cred plugins</a></li><li><a href="#auto14">Building a cred plugin</a></li></ul><li><a href="#auto15">Conclusion</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Goals<a name="auto0"/></h2>
+
+<p>Cred is a pluggable authentication system for servers. It allows any
+number of network protocols to connect and authenticate to a system, and
+communicate to those aspects of the system which are meaningful to the specific
+protocol. For example, Twisted's POP3 support passes a <q>username and
+password</q> set of credentials to get back a mailbox for the specified email
+account. IMAP does the same, but retrieves a slightly different view of the
+same mailbox, enabling those features specific to IMAP which are not available
+in other mail protocols.</p>
+
+<p>Cred is designed to allow both the backend implementation of the business
+logic - called the <em>avatar</em> - and the authentication database - called
+the <em>credential checker</em> - to be decided during deployment. For example,
+the same POP3 server should be able to authenticate against the local UNIX
+password database or an LDAP server without having to know anything about how
+or where mail is stored. </p>
+
+<p>To sketch out how this works - a <q>Realm</q> corresponds to an application
+domain and is in charge of avatars, which are network-accessible business logic
+objects. To connect this to an authentication database, a top-level object
+called a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.Portal.html" title="twisted.cred.portal.Portal">Portal</a></code> stores a
+realm, and a number of credential checkers. Something that wishes to log in,
+such as a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">Protocol</a></code>,
+stores a reference to the portal. Login consists of passing credentials and a
+request interface (e.g. POP3's <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.mail.pop3.IMailbox.html" title="twisted.mail.pop3.IMailbox">IMailbox</a></code>) to the portal. The portal passes
+the credentials to the appropriate credential checker, which returns an avatar
+ID. The ID is passed to the realm, which returns the appropriate avatar. For a
+Portal that has a realm that creates mailbox objects and a credential checker
+that checks /etc/passwd, login consists of passing in a username/password and
+the IMailbox interface to the portal. The portal passes this to the /etc/passwd
+credential checker, gets back a avatar ID corresponding to an email account,
+passes that to the realm and gets back a mailbox object for that email
+account.</p>
+
+<p>Putting all this together, here's how a login request will typically be
+processed:</p>
+
+<img src="../img/cred-login.png" title="Cred Login"/>
+
+ <h2>Cred objects<a name="auto1"/></h2>
+ <h3>The Portal<a name="auto2"/></h3>
+<p>This is the the core of login, the point of integration between all the objects
+in the cred system. There is one
+concrete implementation of Portal, and no interface - it does a very
+simple task. A <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.Portal.html" title="twisted.cred.portal.Portal">Portal</a></code>
+associates one (1) Realm with a collection of
+CredentialChecker instances. (More on those later.)</p>
+
+<p>If you are writing a protocol that needs to authenticate against
+something, you will need a reference to a Portal, and to nothing else.
+This has only 2 methods -</p>
+
+<ul>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.Portal.login.html" title="twisted.cred.portal.Portal.login">login</a></code><code>(credentials, mind, *interfaces)</code>
+
+<p>The docstring is quite expansive (see <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.html" title="twisted.cred.portal">twisted.cred.portal</a></code>), but in
+brief, this is what you call when you need to call in order to connect
+a user to the system. Typically you only pass in one interface, and the mind
+is <code class="python">None</code>. The interfaces are the possible interfaces the returned
+avatar is expected to implement, in order of preference.
+The result is a deferred which fires a tuple of:</p>
+ <ul>
+ <li>interface the avatar implements (which was one of the interfaces passed in the <code>*interfaces</code>
+tuple)</li>
+ <li>an object that implements that interface (an avatar)</li>
+ <li>logout, a 0-argument callable which disconnects the connection that was
+established by this call to login</li>
+ </ul>
+<p>The logout method has to be called when the avatar is logged out. For POP3 this means
+when the protocol is disconnected or logged out, etc..</p>
+</li>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.Portal.registerChecker.html" title="twisted.cred.portal.Portal.registerChecker">registerChecker</a></code><code>(checker, *credentialInterfaces)</code>
+
+<p>which adds a CredentialChecker to the portal. The optional list of interfaces are interfaces of credentials
+that the checker is able to check.</p>
+</li></ul>
+
+ <h3>The CredentialChecker<a name="auto3"/></h3>
+
+<p>This is an object implementing <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.checkers.ICredentialsChecker.html" title="twisted.cred.checkers.ICredentialsChecker">ICredentialsChecker</a></code> which resolves some
+credentials to an avatar ID.
+
+Whether the credentials are stored in an in-memory data structure, an
+Apache-style htaccess file, a UNIX password database, an SSH key database,
+or any other form, an implementation of <code>ICredentialsChecker</code> is
+how this data is connected to cred.
+
+A credential checker
+stipulates some requirements of the credentials it can check by
+specifying a credentialInterfaces attribute, which is a list of
+interfaces. Credentials passed to its requestAvatarId method must
+implement one of those interfaces.</p>
+
+<p>For the most part, these things will just check usernames and passwords
+and produce the username as the result, but hopefully we will be seeing
+some public-key, challenge-response, and certificate based credential
+checker mechanisms soon.</p>
+
+<p>A credential checker should raise an error if it cannot authenticate
+the user, and return <code>twisted.cred.checkers.ANONYMOUS</code>
+for anonymous access.</p>
+
+ <h3>The Credentials<a name="auto4"/></h3>
+<p>Oddly enough, this represents some credentials that the user presents.
+Usually this will just be a small static blob of data, but in some
+cases it will actually be an object connected to a network protocol.
+For example, a username/password pair is static, but a
+challenge/response server is an active state-machine that will require
+several method calls in order to determine a result.</p>
+
+<p>Twisted comes with a number of credentials interfaces and implementations
+in the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.credentials.html" title="twisted.cred.credentials">twisted.cred.credentials</a></code> module,
+such as <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.credentials.IUsernamePassword.html" title="twisted.cred.credentials.IUsernamePassword">IUsernamePassword</a></code>
+and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.credentials.IUsernameHashedPassword.html" title="twisted.cred.credentials.IUsernameHashedPassword">IUsernameHashedPassword</a></code>.</p>
+
+ <h3>The Realm<a name="auto5"/></h3>
+<p>A realm is an interface which connects your universe of <q>business
+objects</q> to the authentication system.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.IRealm.html" title="twisted.cred.portal.IRealm">IRealm</a></code> is another one-method interface:</p>
+
+<ul>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.IRealm.requestAvatar.html" title="twisted.cred.portal.IRealm.requestAvatar">requestAvatar</a></code><code>(avatarId, mind, *interfaces)</code>
+
+<p>This method will typically be called from 'Portal.login'. The avatarId
+is the one returned by a CredentialChecker.</p>
+
+<div class="note"><strong>Note: </strong>Note that <code>avatarId</code> must always be a string. In
+particular, do not use unicode strings. If internationalized support is needed,
+it is recommended to use UTF-8, and take care of decoding in the realm. </div>
+
+<p>The important thing to realize about this method is that if it is being
+called, <em>the user has already authenticated</em>. Therefore, if possible,
+the Realm should create a new user if one does not already exist
+whenever possible. Of course, sometimes this will be impossible
+without more information, and that is the case that the interfaces
+argument is for.</p>
+</li>
+</ul>
+
+<p>Since requestAvatar should be called from a Deferred callback, it may
+return a Deferred or a synchronous result.</p>
+
+ <h3>The Avatar<a name="auto6"/></h3>
+
+<p>An avatar is a business logic object for a specific user. For POP3, it's
+a mailbox, for a first-person-shooter it's the object that interacts with
+the game, the actor as it were. Avatars are specific to an application,
+and each avatar represents a single <q>user</q>.</p>
+
+ <h3>The Mind<a name="auto7"/></h3>
+
+<p>As mentioned before, the mind is usually <code>None</code>, so you can skip this
+bit if you want.</p>
+
+<p>Masters of Perspective Broker already know this object as the ill-named
+<q>client object</q>. There is no <q>mind</q> class, or even interface, but it
+is an object which serves an important role - any notifications which are to be
+relayed to an authenticated client are passed through a 'mind'. In addition, it
+allows passing more information to the realm during login in addition to the
+avatar ID.</p>
+
+<p>The name may seem rather unusual, but considering that a Mind is
+representative of the entity on the <q>other end</q> of a network connection
+that is both receiving updates and issuing commands, I believe it is
+appropriate.</p>
+
+<p>Although many protocols will not use this, it serves an important role.
+ It is provided as an argument both to the Portal and to the Realm,
+although a CredentialChecker should interact with a client program
+exclusively through a Credentials instance.</p>
+
+<p>Unlike the original Perspective Broker <q>client object</q>, a Mind's
+implementation is most often dictated by the protocol that is
+connecting rather than the Realm. A Realm which requires a particular
+interface to issue notifications will need to wrap the Protocol's mind
+implementation with an adapter in order to get one that conforms to its
+expected interface - however, Perspective Broker will likely continue
+to use the model where the client object has a pre-specified remote
+interface.</p>
+
+<p>(If you don't quite understand this, it's fine. It's hard to explain,
+and it's not used in simple usages of cred, so feel free to pass None
+until you find yourself requiring something like this.)</p>
+
+ <h2>Responsibilities<a name="auto8"/></h2>
+
+ <h3>Server protocol implementation<a name="auto9"/></h3>
+
+<p>The protocol implementor should define the interface the avatar should implement,
+and design the protocol to have a portal attached. When a user logs in using the
+protocol, a credential object is created, passed to the portal, and an avatar
+with the appropriate interface is requested. When the user logs out or the protocol
+is disconnected, the avatar should be logged out.</p>
+
+<p>The protocol designer should not hardcode how users are authenticated or the
+realm implemented. For example, a POP3 protocol implementation would require a portal whose
+realm returns avatars implementing IMailbox and whose credential checker accepts
+username/password credentials, but that is all. Here's a sketch of how the code
+might look - note that USER and PASS are the protocol commands used to login, and
+the DELE command can only be used after you are logged in:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>, <span class="py-src-variable">error</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMailbox</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Interface specification for mailbox.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">deleteMessage</span>(<span class="py-src-parameter">index</span>): <span class="py-src-keyword">pass</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">POP3</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-comment"># ...</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">portal</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">portal</span> = <span class="py-src-variable">portal</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">do_DELE</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">i</span>):
+ <span class="py-src-comment"># uses self.mbox, which is set after login</span>
+ <span class="py-src-variable">i</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">i</span>)-<span class="py-src-number">1</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">mbox</span>.<span class="py-src-variable">deleteMessage</span>(<span class="py-src-variable">i</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">successResponse</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">do_USER</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_userIs</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">successResponse</span>(<span class="py-src-string">'USER accepted, send PASS'</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">do_PASS</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">password</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_userIs</span> <span class="py-src-keyword">is</span> <span class="py-src-variable">None</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">failResponse</span>(<span class="py-src-string">&quot;USER required before PASS&quot;</span>)
+ <span class="py-src-keyword">return</span>
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">_userIs</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_userIs</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">maybeDeferred</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">authenticateUserPASS</span>, <span class="py-src-variable">user</span>, <span class="py-src-variable">password</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cbMailbox</span>, <span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">authenticateUserPASS</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">password</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">is</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">None</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">portal</span>.<span class="py-src-variable">login</span>(
+ <span class="py-src-variable">cred</span>.<span class="py-src-variable">credentials</span>.<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">password</span>),
+ <span class="py-src-variable">None</span>,
+ <span class="py-src-variable">IMailbox</span>
+ )
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">error</span>.<span class="py-src-variable">UnauthorizedLogin</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cbMailbox</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">ial</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">interface</span>, <span class="py-src-variable">avatar</span>, <span class="py-src-variable">logout</span> = <span class="py-src-variable">ial</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">interface</span> <span class="py-src-keyword">is</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">IMailbox</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">failResponse</span>(<span class="py-src-string">'Authentication failed'</span>)
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>(<span class="py-src-string">&quot;_cbMailbox() called with an interface other than IMailbox&quot;</span>)
+ <span class="py-src-keyword">return</span>
+
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">mbox</span> = <span class="py-src-variable">avatar</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_onLogout</span> = <span class="py-src-variable">logout</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">successResponse</span>(<span class="py-src-string">'Authentication succeeded'</span>)
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">msg</span>(<span class="py-src-string">&quot;Authenticated login for &quot;</span> + <span class="py-src-variable">user</span>)
+</pre>
+
+ <h3>Application implementation<a name="auto10"/></h3>
+
+<p>The application developer can implement realms and credential checkers. For example,
+she might implement a realm that returns IMailbox implementing avatars, using MySQL
+for storage, or perhaps a credential checker that uses LDAP for authentication.
+In the following example, the Realm for a simple remote object service (using
+Twisted's Perspective Broker protocol) is implemented:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimplePerspective</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">text</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'echoing'</span>,<span class="py-src-variable">text</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">text</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">logout</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">self</span>, <span class="py-src-string">&quot;logged out&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-variable">avatar</span> = <span class="py-src-variable">SimplePerspective</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">avatar</span>, <span class="py-src-variable">avatar</span>.<span class="py-src-variable">logout</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>(<span class="py-src-string">&quot;no interface&quot;</span>)
+</pre>
+
+ <h3>Deployment<a name="auto11"/></h3>
+
+<p>Deployment involves tying together a protocol, an appropriate realm and a credential
+checker. For example, a POP3 server can be constructed by attaching to it a portal
+that wraps the MySQL-based realm and an /etc/passwd credential checker, or perhaps
+the LDAP credential checker if that is more useful. The following example shows
+how the SimpleRealm in the previous example is deployed using an in-memory credential checker:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>
+
+<span class="py-src-variable">portal</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">SimpleRealm</span>())
+<span class="py-src-variable">checker</span> = <span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>()
+<span class="py-src-variable">checker</span>.<span class="py-src-variable">addUser</span>(<span class="py-src-string">&quot;guest&quot;</span>, <span class="py-src-string">&quot;password&quot;</span>)
+<span class="py-src-variable">portal</span>.<span class="py-src-variable">registerChecker</span>(<span class="py-src-variable">checker</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">9986</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">portal</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h2>Cred plugins<a name="auto12"/></h2>
+
+ <h3>Authentication with cred plugins<a name="auto13"/></h3>
+
+<p> Cred offers a plugin architecture for authentication methods. The
+primary API for this architecture is the command-line; the plugins are
+meant to be specified by the end-user when deploying a TAP (twistd
+plugin).</p>
+
+<p> For more information on writing a twistd plugin and using cred
+plugins for your application, please refer to the <a href="tap.html" shape="rect">Writing a twistd plugin</a> document.</p>
+
+ <h3>Building a cred plugin<a name="auto14"/></h3>
+
+<p> To build a plugin for cred, you should first define an <code class="python">authType</code>, a short one-word string that defines
+your plugin to the command-line. Once you have this, the convention is
+to create a file named <code>myapp_plugins.py</code> in the
+<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.plugins.html" title="twisted.plugins">twisted.plugins</a></code> module path. </p>
+
+<p> Below is an example file structure for an application that defines
+such a plugin: </p>
+
+<ul>
+<li>MyApplication/
+ <ul>
+ <li>setup.py</li>
+ <li>myapp/
+ <ul>
+ <li>__init__.py</li>
+ <li>cred.py</li>
+ <li>server.py</li>
+ </ul>
+ </li>
+ <li>twisted/
+ <ul>
+ <li>plugins/
+ <ul>
+ <li>myapp_plugins.py</li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+</li>
+</ul>
+
+<p>
+Once you have created this structure within your application, you can
+create the code for your cred plugin by building a factory class which
+implements <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.strcred.ICheckerFactory.html" title="twisted.cred.strcred.ICheckerFactory">ICheckerFactory</a></code>.
+These factory classes should not consist of a tremendous amount of
+code. Most of the real application logic should reside in the cred
+checker itself. (For help on building those, scroll up.)
+</p>
+
+<p>
+The core purpose of the CheckerFactory is to translate an <code class="python">argstring</code>, which is passed on the command line,
+into a suitable set of initialization parameters for a Checker
+class. In most cases this should be little more than constructing a
+dictionary or a tuple of arguments, then passing them along to a new
+checker instance.
+</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">plugin</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">strcred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ICheckerFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myapp</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SpecialChecker</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SpecialCheckerFactory</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ A checker factory for a specialized (fictional) API.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-comment"># The class needs to implement both of these interfaces</span>
+ <span class="py-src-comment"># for the plugin system to find our factory.</span>
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">ICheckerFactory</span>, <span class="py-src-variable">plugin</span>.<span class="py-src-variable">IPlugin</span>)
+
+ <span class="py-src-comment"># This tells AuthOptionsMixin how to find this factory.</span>
+ <span class="py-src-variable">authType</span> = <span class="py-src-string">&quot;special&quot;</span>
+
+ <span class="py-src-comment"># This is a one-line explanation of what arguments, if any,</span>
+ <span class="py-src-comment"># your particular cred plugin requires at the command-line.</span>
+ <span class="py-src-variable">argStringFormat</span> = <span class="py-src-string">&quot;A colon-separated key=value list.&quot;</span>
+
+ <span class="py-src-comment"># This help text can be multiple lines. It will be displayed</span>
+ <span class="py-src-comment"># when someone uses the &quot;--help-auth-type special&quot; command.</span>
+ <span class="py-src-variable">authHelp</span> = <span class="py-src-string">&quot;&quot;&quot;Some help text goes here ...&quot;&quot;&quot;</span>
+
+ <span class="py-src-comment"># This will be called once per command-line.</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">generateChecker</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">argstring</span>=<span class="py-src-string">&quot;&quot;</span>):
+ <span class="py-src-variable">argdict</span> = <span class="py-src-variable">dict</span>((<span class="py-src-variable">x</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'='</span>) <span class="py-src-keyword">for</span> <span class="py-src-variable">x</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">argstring</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>)))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">SpecialChecker</span>(**<span class="py-src-variable">dict</span>)
+
+<span class="py-src-comment"># We need to instantiate our class for the plugin to work.</span>
+<span class="py-src-variable">theSpecialCheckerFactory</span> = <span class="py-src-variable">SpecialCheckerFactory</span>()
+</pre>
+
+<p> For more information on how your plugin can be used in your
+application (and by other application developers), please see the <a href="tap.html" shape="rect">Writing a twistd plugin</a> document.</p>
+
+<h2>Conclusion<a name="auto15"/></h2>
+
+<p>After reading through this tutorial, you should be able to
+</p>
+<ul>
+<li>Understand how the cred architecture applies to your application</li>
+<li>Integrate your application with cred's object model</li>
+<li>Deploy an application that uses cred for authentication</li>
+<li>Allow your users to use command-line authentication plugins</li>
+</ul>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/debug-with-emacs.html b/doc/core/howto/debug-with-emacs.html
new file mode 100644
index 0000000..48a2a4d
--- /dev/null
+++ b/doc/core/howto/debug-with-emacs.html
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Debugging Python(Twisted) with Emacs</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Debugging Python(Twisted) with Emacs</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<img src="http://yellow5.com/pokey/archive/pokey411_3.gif"/>
+<a href="#footnote-1" title="POKEY THE PENGUIN IS COPYRIGHT © 1998-2002 THE AUTHORS"><super>1</super></a>
+
+<ul>
+ <li>Open up your project files. sometimes emacs can't find them if you
+ don't have them open before-hand.</li>
+
+ <li>Make sure you have a program called <code class="shell">pdb</code> somewhere
+ in your PATH, with the following contents:
+
+ <pre class="shell" xml:space="preserve">#!/bin/sh
+exec python2.3 /usr/lib/python2.3/pdb.py $1 $2 $3 $4 $5 $6 $7 $8 $9
+ </pre></li>
+
+ <li>Run <code class="shell">M-x pdb</code> in emacs. If you usually run your
+ program as <code class="shell">python foo.py</code>, your command line should be <code class="shell">pdb
+ foo.py</code>, for <code class="shell">twistd</code> and <code class="shell">trial</code> just
+ add -b to the command line, e.g.: <code class="shell">twistd -b -y my.tac</code></li>
+
+ <li>While pdb waits for your input, go to a place in your code and hit
+ <code class="shell">C-x SPC</code> to insert a break-point. pdb should say something happy.
+ Do this in as many points as you wish.</li>
+
+ <li>Go to your pdb buffer and hit <code class="shell">c</code>; this runs as normal until a
+ break-point is found.</li>
+
+ <li>Once you get to a breakpoint, use <code class="shell">s</code> to step, <code class="shell">n</code> to run the
+ current line without stepping through the functions it calls, <code class="shell">w</code>
+ to print out the current stack, <code class="shell">u</code> and <code class="shell">d</code> to go up and down a
+ level in the stack, <code class="shell">p foo</code> to print result of expression <code class="shell">foo</code>.</li>
+
+ <li>Recommendations for effective debugging:
+ <ul>
+ <li>use <code class="shell">p self</code> a lot; just knowing the class where the current code
+ is isn't enough most of the time.</li>
+ <li>use <code class="shell">w</code> to get your bearings, it'll re-display the current-line/arrow</li>
+ <li>after you use <code class="shell">w</code>, use <code class="shell">u</code> and <code class="shell">d</code> and lots more <code class="shell">p self</code> on the
+ different stack-levels.</li>
+ <li>If you've got a big code-path that you need to grok, keep another
+ buffer open and list the code-path there (e.g., I had a
+ nasty-evil Deferred recursion, and this helped me tons)</li>
+ </ul>
+ </li>
+</ul>
+
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">POKEY THE PENGUIN IS COPYRIGHT © 1998-2002
+THE AUTHORS</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/defer.html b/doc/core/howto/defer.html
new file mode 100644
index 0000000..492175b
--- /dev/null
+++ b/doc/core/howto/defer.html
@@ -0,0 +1,898 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Deferred Reference</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Deferred Reference</h1>
+ <div class="toc"><ol><li><a href="#auto0">Deferreds</a></li><li><a href="#auto1">Callbacks</a></li><ul><li><a href="#auto2">Multiple callbacks</a></li><li><a href="#auto3">Visual Explanation</a></li></ul><li><a href="#auto4">Errbacks</a></li><ul><li><a href="#auto5">Unhandled Errors</a></li></ul><li><a href="#auto6">Handling either synchronous or asynchronous results</a></li><ul><li><a href="#auto7">Handling possible Deferreds in the library code</a></li></ul><li><a href="#auto8">DeferredList</a></li><ul><li><a href="#auto9">Other behaviours</a></li><li><a href="#auto10">gatherResults</a></li></ul><li><a href="#auto11">Class Overview</a></li><ul><li><a href="#auto12">Basic Callback Functions</a></li><li><a href="#auto13">Chaining Deferreds</a></li></ul><li><a href="#auto14">See also</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<p>This document is a guide to the behaviour of the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code> object, and to various
+ways you can use them when they are returned by functions.</p>
+
+<p>This document assumes that you are familiar with the basic principle that
+the Twisted framework is structured around: asynchronous, callback-based
+programming, where instead of having blocking code in your program or using
+threads to run blocking code, you have functions that return immediately and
+then begin a callback chain when data is available.</p>
+
+<p>
+After reading this document, the reader should expect to be able to
+deal with most simple APIs in Twisted and Twisted-using code that
+return Deferreds.
+</p>
+
+<ul>
+<li>what sorts of things you can do when you get a Deferred from a
+function call; and</li>
+<li>how you can write your code to robustly handle errors in Deferred
+code.</li>
+</ul>
+
+<a name="deferreds" shape="rect"/>
+<h2>Deferreds<a name="auto0"/></h2>
+
+<p>Twisted uses the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> object to manage the callback
+sequence. The client application attaches a series of functions to the
+deferred to be called in order when the results of the asychronous request are
+available (this series of functions is known as a series of
+<strong>callbacks</strong>, or a <strong>callback chain</strong>), together
+with a series of functions to be called if there is an error in the
+asychronous request (known as a series of <strong>errbacks</strong> or an <strong>errback chain</strong>). The asychronous library code calls the first
+callback when the result is available, or the first errback when an error
+occurs, and the <code>Deferred</code> object then hands the results of each
+callback or errback function to the next function in the chain.</p>
+
+<h2>Callbacks<a name="auto1"/></h2>
+
+<p>A <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code> is a promise that
+a function will at some point have a result. We can attach callback functions
+to a Deferred, and once it gets a result these callbacks will be called. In
+addition Deferreds allow the developer to register a callback for an error,
+with the default behavior of logging the error. The deferred mechanism
+standardizes the application programmer's interface with all sorts of
+blocking or delayed operations.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getDummyData</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ This function is a dummy which simulates a delayed result and
+ returns a Deferred which will fire with that result. Don't try too
+ hard to understand this.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-comment"># simulate a delayed result by asking the reactor to fire the</span>
+ <span class="py-src-comment"># Deferred in 2 seconds time with the result x * 3</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">2</span>, <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>, <span class="py-src-variable">x</span> * <span class="py-src-number">3</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printData</span>(<span class="py-src-parameter">d</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Data handling function to be added as a callback: handles the
+ data by printing the result
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-variable">d</span> = <span class="py-src-variable">getDummyData</span>(<span class="py-src-number">3</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printData</span>)
+
+<span class="py-src-comment"># manually set up the end of the process by asking the reactor to</span>
+<span class="py-src-comment"># stop itself in 4 seconds time</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">4</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>)
+<span class="py-src-comment"># start up the Twisted reactor (event loop handler) manually</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h3>Multiple callbacks<a name="auto2"/></h3>
+
+<p>Multiple callbacks can be added to a Deferred. The first callback in the
+Deferred's callback chain will be called with the result, the second with the
+result of the first callback, and so on. Why do we need this? Well, consider
+a Deferred returned by twisted.enterprise.adbapi - the result of a SQL query.
+A web widget might add a callback that converts this result into HTML, and
+pass the Deferred onwards, where the callback will be used by twisted to
+return the result to the HTTP client. The callback chain will be bypassed in
+case of errors or exceptions.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Getter</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">gotResults</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ The Deferred mechanism provides a mechanism to signal error
+ conditions. In this case, odd numbers are bad.
+
+ This function demonstrates a more complex way of starting
+ the callback chain by checking for expected results and
+ choosing whether to fire the callback or errback chain
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">x</span> % <span class="py-src-number">2</span> == <span class="py-src-number">0</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">x</span>*<span class="py-src-number">3</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">ValueError</span>(<span class="py-src-string">&quot;You used an odd number!&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_toHTML</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">r</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ This function converts r to HTML.
+
+ It is added to the callback chain by getDummyData in
+ order to demonstrate how a callback passes its own result
+ to the next callback
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Result: %s&quot;</span> % <span class="py-src-variable">r</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getDummyData</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ The Deferred mechanism allows for chained callbacks.
+ In this example, the output of gotResults is first
+ passed through _toHTML on its way to printData.
+
+ Again this function is a dummy, simulating a delayed result
+ using callLater, rather than using a real asynchronous
+ setup.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-comment"># simulate a delayed result by asking the reactor to schedule</span>
+ <span class="py-src-comment"># gotResults in 2 seconds time</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">2</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">gotResults</span>, <span class="py-src-variable">x</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_toHTML</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printData</span>(<span class="py-src-parameter">d</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printError</span>(<span class="py-src-parameter">failure</span>):
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">stderr</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">str</span>(<span class="py-src-variable">failure</span>))
+
+<span class="py-src-comment"># this series of callbacks and errbacks will print an error message</span>
+<span class="py-src-variable">g</span> = <span class="py-src-variable">Getter</span>()
+<span class="py-src-variable">d</span> = <span class="py-src-variable">g</span>.<span class="py-src-variable">getDummyData</span>(<span class="py-src-number">3</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printData</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">printError</span>)
+
+<span class="py-src-comment"># this series of callbacks and errbacks will print &quot;Result: 12&quot;</span>
+<span class="py-src-variable">g</span> = <span class="py-src-variable">Getter</span>()
+<span class="py-src-variable">d</span> = <span class="py-src-variable">g</span>.<span class="py-src-variable">getDummyData</span>(<span class="py-src-number">4</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printData</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">printError</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">4</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h3>Visual Explanation<a name="auto3"/></h3>
+
+<div align="center" hlint="off">
+<img src="../img/deferred-attach.png"/>
+</div>
+
+<ol>
+ <li>Requesting method (data sink) requests data, gets
+ Deferred object.</li>
+
+ <li>Requesting method attaches callbacks to Deferred
+ object.</li>
+</ol>
+<img align="left" hlint="off" src="../img/deferred-process.png"/>
+
+<ol>
+
+ <li>When the result is ready, give it to the Deferred
+ object. <code>.callback(result)</code> if the operation succeeded,
+ <code>.errback(failure)</code> if it failed. Note that
+ <code>failure</code> is typically an instance of a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">twisted.python.failure.Failure</a></code>
+ instance.</li>
+
+ <li>Deferred object triggers previously-added (call/err)back
+ with the <code>result</code> or <code>failure</code>.
+ Execution then follows the following rules, going down the
+ chain of callbacks to be processed.
+
+ <ul>
+ <li>Result of the callback is always passed as the first
+ argument to the next callback, creating a chain of
+ processors.</li>
+
+ <li>If a callback raises an exception, switch to
+ errback.</li>
+
+ <li>An unhandled failure gets passed down the line of
+ errbacks, this creating an asynchronous analog to a
+ series to a series of <code>except:</code>
+ statements.</li>
+
+ <li>If an errback doesn't raise an exception or return a
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">twisted.python.failure.Failure</a></code>
+ instance, switch to callback.</li>
+ </ul> </li>
+</ol>
+<br clear="all" hlint="off"/>
+
+<h2>Errbacks<a name="auto4"/></h2>
+
+<p>Deferred's error handling is modeled after Python's
+exception handling. In the case that no errors occur, all the
+callbacks run, one after the other, as described above.</p>
+
+<p>If the errback is called instead of the callback (e.g. because a DB query
+raised an error), then a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">twisted.python.failure.Failure</a></code> is passed into the first
+errback (you can add multiple errbacks, just like with callbacks). You can
+think of your errbacks as being like <code class="python">except</code> blocks
+of ordinary Python code.</p>
+
+<p>Unless you explicitly <code class="python">raise</code> an error in except
+block, the <code class="python">Exception</code> is caught and stops
+propagating, and normal execution continues. The same thing happens with
+errbacks: unless you explicitly <code class="python">return</code> a <code class="python">Failure</code> or (re-)raise an exception, the error stops
+propagating, and normal callbacks continue executing from that point (using the
+value returned from the errback). If the errback does returns a <code class="python">Failure</code> or raise an exception, then that is passed to the
+next errback, and so on.</p>
+
+<p><em>Note:</em> If an errback doesn't return anything, then it effectively
+returns <code class="python">None</code>, meaning that callbacks will continue
+to be executed after this errback. This may not be what you expect to happen,
+so be careful. Make sure your errbacks return a <code class="python">Failure</code> (probably the one that was passed to it), or a
+meaningful return value for the next callback.</p>
+
+<p>Also, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">twisted.python.failure.Failure</a></code> instances have
+a useful method called trap, allowing you to effectively do the equivalent
+of:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">try</span>:
+ <span class="py-src-comment"># code that may throw an exception</span>
+ <span class="py-src-variable">cookSpamAndEggs</span>()
+<span class="py-src-keyword">except</span> (<span class="py-src-variable">SpamException</span>, <span class="py-src-variable">EggException</span>):
+ <span class="py-src-comment"># Handle SpamExceptions and EggExceptions</span>
+ ...
+</pre>
+
+<p>You do this by:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">errorHandler</span>(<span class="py-src-parameter">failure</span>):
+ <span class="py-src-variable">failure</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">SpamException</span>, <span class="py-src-variable">EggException</span>)
+ <span class="py-src-comment"># Handle SpamExceptions and EggExceptions</span>
+
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cookSpamAndEggs</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">errorHandler</span>)
+</pre>
+
+<p>If none of arguments passed to <code class="python">failure.trap</code>
+match the error encapsulated in that <code class="python">Failure</code>, then
+it re-raises the error.</p>
+
+<p>There's another potential <q>gotcha</q> here. There's a
+method <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.addCallbacks.html" title="twisted.internet.defer.Deferred.addCallbacks">twisted.internet.defer.Deferred.addCallbacks</a></code>
+which is similar to, but not exactly the same as, <code class="python">addCallback</code> followed by <code class="python">addErrback</code>. In particular, consider these two cases:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-comment"># Case 1</span>
+<span class="py-src-variable">d</span> = <span class="py-src-variable">getDeferredFromSomewhere</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">callback1</span>) <span class="py-src-comment"># A</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">errback1</span>) <span class="py-src-comment"># B</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">callback2</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">errback2</span>)
+
+<span class="py-src-comment"># Case 2</span>
+<span class="py-src-variable">d</span> = <span class="py-src-variable">getDeferredFromSomewhere</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">callback1</span>, <span class="py-src-variable">errback1</span>) <span class="py-src-comment"># C</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">callback2</span>, <span class="py-src-variable">errback2</span>)
+</pre>
+
+<p>If an error occurs in <code class="python">callback1</code>, then for Case 1
+<code class="python">errback1</code> will be called with the failure. For Case
+2, <code class="python">errback2</code> will be called. Be careful with your
+callbacks and errbacks.</p>
+
+<p>What this means in a practical sense is in Case 1, the callback in line
+A will handle a success condition from <code>getDeferredFromSomewhere</code>,
+and the errback in line B will handle any errors that occur <em>from either the
+upstream source, or that occur in A</em>. In Case 2, the errback in line C <em>will
+only handle an error condition raised by</em> <code>getDeferredFromSomewhere</code>,
+it will not do any handling of errors
+raised in <code>callback1</code>.</p>
+
+
+<h3>Unhandled Errors<a name="auto5"/></h3>
+
+<p>If a Deferred is garbage-collected with an unhandled error (i.e. it would
+call the next errback if there was one), then Twisted will write the error's
+traceback to the log file. This means that you can typically get away with not
+adding errbacks and still get errors logged. Be careful though; if you keep a
+reference to the Deferred around, preventing it from being garbage-collected,
+then you may never see the error (and your callbacks will mysteriously seem to
+have never been called). If unsure, you should explicitly add an errback after
+your callbacks, even if all you do is:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-comment"># Make sure errors get logged</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>)
+</pre>
+
+<h2>Handling either synchronous or asynchronous results<a name="auto6"/></h2>
+<p>
+In some applications, there are functions that might be either asynchronous or
+synchronous. For example, a user authentication function might be able to
+check in memory whether a user is authenticated, allowing the authentication
+function to return an immediate result, or it may need to wait on
+network data, in which case it should return a Deferred to be fired
+when that data arrives. However, a function that wants to check if a user is
+authenticated will then need to accept both immediate results <em> and</em>
+Deferreds.
+</p>
+
+<p>
+In this example, the library function <code>authenticateUser</code> uses the
+application function <code>isValidUser</code> to authenticate a user:
+</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">authenticateUser</span>(<span class="py-src-parameter">isValidUser</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">isValidUser</span>(<span class="py-src-variable">user</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;User is authenticated&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;User is not authenticated&quot;</span>
+</pre>
+
+<p>
+However, it assumes that <code>isValidUser</code> returns immediately,
+whereas <code>isValidUser</code> may actually authenticate the user
+asynchronously and return a Deferred. It is possible to adapt this
+trivial user authentication code to accept either a
+synchronous <code>isValidUser</code> or an
+asynchronous <code>isValidUser</code>, allowing the library to handle
+either type of function. It is, however, also possible to adapt
+synchronous functions to return Deferreds. This section describes both
+alternatives: handling functions that might be synchronous or
+asynchronous in the library function (<code>authenticateUser</code>)
+or in the application code.
+</p>
+
+<h3>Handling possible Deferreds in the library code<a name="auto7"/></h3>
+
+<p>
+Here is an example of a synchronous user authentication function that might be
+passed to <code>authenticateUser</code>:
+</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">synchronousIsValidUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">'''
+ Return true if user is a valid user, false otherwise
+ '''</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">&quot;Alice&quot;</span>, <span class="py-src-string">&quot;Angus&quot;</span>, <span class="py-src-string">&quot;Agnes&quot;</span>]
+</pre><div class="caption">Source listing - <a href="listings/deferred/synch-validation.py"><span class="filename">listings/deferred/synch-validation.py</span></a></div></div>
+
+<p>
+However, here's an <code>asynchronousIsValidUser</code> function that returns
+a Deferred:
+</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">asynchronousIsValidUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">2</span>, <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>, <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">&quot;Alice&quot;</span>, <span class="py-src-string">&quot;Angus&quot;</span>, <span class="py-src-string">&quot;Agnes&quot;</span>])
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+</pre>
+
+<p> Our original implementation of <code>authenticateUser</code> expected <code>isValidUser</code> to be synchronous, but now we need to change it to handle both
+synchronous and asynchronous implementations of <code>isValidUser</code>. For this, we
+use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.maybeDeferred.html" title="twisted.internet.defer.maybeDeferred">maybeDeferred</a></code> to
+call <code>isValidUser</code>, ensuring that the result of <code>isValidUser</code> is a Deferred,
+even if <code>isValidUser</code> is a synchronous function:
+</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">result</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;User is authenticated&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;User is not authenticated&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">authenticateUser</span>(<span class="py-src-parameter">isValidUser</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">maybeDeferred</span>(<span class="py-src-variable">isValidUser</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+</pre>
+
+<p>
+Now <code>isValidUser</code> could be either <code>synchronousIsValidUser</code> or <code>asynchronousIsValidUser</code>.
+</p>
+
+<p>It is also possible to modify <code>synchronousIsValidUser</code> to return
+a Deferred, see <a href="gendefer.html" shape="rect">Generating Deferreds</a> for more
+information.</p>
+
+<a name="deferredlist" shape="rect"/>
+<h2>DeferredList<a name="auto8"/></h2>
+
+<p>Sometimes you want to be notified after several different events have all
+happened, rather than waiting for each one individually. For example, you may
+want to wait for all the connections in a list to close. <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.DeferredList.html" title="twisted.internet.defer.DeferredList">twisted.internet.defer.DeferredList</a></code> is the way to do
+this.</p>
+
+<p>To create a DeferredList from multiple Deferreds, you simply pass a list of
+the Deferreds you want it to wait for:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-comment"># Creates a DeferredList</span>
+<span class="py-src-variable">dl</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">DeferredList</span>([<span class="py-src-variable">deferred1</span>, <span class="py-src-variable">deferred2</span>, <span class="py-src-variable">deferred3</span>])
+</pre>
+
+<p>You can now treat the DeferredList like an ordinary Deferred; you can call <code>addCallbacks</code> and so on. The DeferredList will call its callback
+when all the deferreds have completed. The callback will be called with a list
+of the results of the Deferreds it contains, like so:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-comment"># A callback that unpacks and prints the results of a DeferredList</span>
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">for</span> (<span class="py-src-variable">success</span>, <span class="py-src-variable">value</span>) <span class="py-src-keyword">in</span> <span class="py-src-variable">result</span>:
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">success</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Success:'</span>, <span class="py-src-variable">value</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Failure:'</span>, <span class="py-src-variable">value</span>.<span class="py-src-variable">getErrorMessage</span>()
+
+<span class="py-src-comment"># Create three deferreds.</span>
+<span class="py-src-variable">deferred1</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">deferred2</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">deferred3</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+
+<span class="py-src-comment"># Pack them into a DeferredList</span>
+<span class="py-src-variable">dl</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">DeferredList</span>([<span class="py-src-variable">deferred1</span>, <span class="py-src-variable">deferred2</span>, <span class="py-src-variable">deferred3</span>], <span class="py-src-variable">consumeErrors</span>=<span class="py-src-variable">True</span>)
+
+<span class="py-src-comment"># Add our callback</span>
+<span class="py-src-variable">dl</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+
+<span class="py-src-comment"># Fire our three deferreds with various values.</span>
+<span class="py-src-variable">deferred1</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">'one'</span>)
+<span class="py-src-variable">deferred2</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">Exception</span>(<span class="py-src-string">'bang!'</span>))
+<span class="py-src-variable">deferred3</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">'three'</span>)
+
+<span class="py-src-comment"># At this point, dl will fire its callback, printing:</span>
+<span class="py-src-comment"># Success: one</span>
+<span class="py-src-comment"># Failure: bang!</span>
+<span class="py-src-comment"># Success: three</span>
+<span class="py-src-comment"># (note that defer.SUCCESS == True, and defer.FAILURE == False)</span>
+</pre>
+
+<p>A standard DeferredList will never call errback, but failures in Deferreds
+passed to a DeferredList will still errback unless <code>consumeErrors</code>
+is passed <code>True</code>. See below for more details about this and other
+flags which modify the behavior of DeferredList.</p>
+
+<div class="note"><strong>Note: </strong>
+<p>If you want to apply callbacks to the individual Deferreds that
+go into the DeferredList, you should be careful about when those callbacks
+are added. The act of adding a Deferred to a DeferredList inserts a callback
+into that Deferred (when that callback is run, it checks to see if the
+DeferredList has been completed yet). The important thing to remember is
+that it is <em>this callback</em> which records the value that goes into the
+result list handed to the DeferredList's callback.</p>
+
+
+
+<p>Therefore, if you add a callback to the Deferred <em>after</em> adding the
+Deferred to the DeferredList, the value returned by that callback will not
+be given to the DeferredList's callback. To avoid confusion, we recommend not
+adding callbacks to a Deferred once it has been used in a DeferredList.</p>
+</div>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">result</span>
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">addTen</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">result</span> + <span class="py-src-string">&quot; ten&quot;</span>
+
+<span class="py-src-comment"># Deferred gets callback before DeferredList is created</span>
+<span class="py-src-variable">deferred1</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">deferred2</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">deferred1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">addTen</span>)
+<span class="py-src-variable">dl</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">DeferredList</span>([<span class="py-src-variable">deferred1</span>, <span class="py-src-variable">deferred2</span>])
+<span class="py-src-variable">dl</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+<span class="py-src-variable">deferred1</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;one&quot;</span>) <span class="py-src-comment"># fires addTen, checks DeferredList, stores &quot;one ten&quot;</span>
+<span class="py-src-variable">deferred2</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;two&quot;</span>)
+<span class="py-src-comment"># At this point, dl will fire its callback, printing:</span>
+<span class="py-src-comment"># [(1, 'one ten'), (1, 'two')]</span>
+
+<span class="py-src-comment"># Deferred gets callback after DeferredList is created</span>
+<span class="py-src-variable">deferred1</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">deferred2</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">dl</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">DeferredList</span>([<span class="py-src-variable">deferred1</span>, <span class="py-src-variable">deferred2</span>])
+<span class="py-src-variable">deferred1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">addTen</span>) <span class="py-src-comment"># will run *after* DeferredList gets its value</span>
+<span class="py-src-variable">dl</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+<span class="py-src-variable">deferred1</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;one&quot;</span>) <span class="py-src-comment"># checks DeferredList, stores &quot;one&quot;, fires addTen</span>
+<span class="py-src-variable">deferred2</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;two&quot;</span>)
+<span class="py-src-comment"># At this point, dl will fire its callback, printing:</span>
+<span class="py-src-comment"># [(1, 'one), (1, 'two')]</span>
+</pre>
+
+<h3>Other behaviours<a name="auto9"/></h3>
+
+<p>DeferredList accepts three keyword arguments that modify its behaviour:
+<code>fireOnOneCallback</code>, <code>fireOnOneErrback</code> and
+<code>consumeErrors</code>. If <code>fireOnOneCallback</code> is set, the
+DeferredList will immediately call its callback as soon as any of its Deferreds
+call their callback. Similarly, <code>fireOnOneErrback</code> will call errback
+as soon as any of the Deferreds call their errback. Note that DeferredList is
+still one-shot, like ordinary Deferreds, so after a callback or errback has been
+called the DeferredList will do nothing further (it will just silently ignore
+any other results from its Deferreds).</p>
+
+<p>The <code>fireOnOneErrback</code> option is particularly useful when you
+want to wait for all the results if everything succeeds, but also want to know
+immediately if something fails.</p>
+
+<p>The <code>consumeErrors</code> argument will stop the DeferredList from
+propagating any errors along the callback chains of any Deferreds it contains
+(usually creating a DeferredList has no effect on the results passed along the
+callbacks and errbacks of their Deferreds). Stopping errors at the DeferredList
+with this option will prevent <q>Unhandled error in Deferred</q> warnings from
+the Deferreds it contains without needing to add extra errbacks<a href="#footnote-1" title="Unless of course a later callback starts a fresh error — but as we've already noted, adding callbacks to a Deferred after its used in a DeferredList is confusing and usually avoided."><super>1</super></a>. Passing a true value
+for the <code>consumeErrors</code> parameter will not change the behavior of <code>fireOnOneCallback</code> or <code>fireOnOneErrback</code>.</p>
+
+<h3>gatherResults<a name="auto10"/></h3>
+
+<p>A common use for DeferredList is to &quot;join&quot; a number of parallel asynchronous
+operations, finishing successfully if all of the operations were successful, or
+failing if any one of the operations fails. In this case, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.gatherResults.html" title="twisted.internet.defer.gatherResults">twisted.internet.defer.gatherResults</a></code> is a useful
+shortcut:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-variable">d1</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">d2</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">gatherResults</span>([<span class="py-src-variable">d1</span>, <span class="py-src-variable">d2</span>], <span class="py-src-variable">consumeErrors</span>=<span class="py-src-variable">True</span>)
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">result</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+<span class="py-src-variable">d1</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;one&quot;</span>)
+<span class="py-src-comment"># nothing is printed yet; d is still awaiting completion of d2</span>
+<span class="py-src-variable">d2</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;two&quot;</span>)
+<span class="py-src-comment"># printResult prints [&quot;one&quot;, &quot;two&quot;]</span>
+</pre>
+
+<p>The <code>consumeErrors</code> argument has the same meaning as it does
+for <a href="#deferredlist" shape="rect"><code>DeferredList</code></a>: if true, it causes
+<code>gatherResults</code> to consume any errors in the passed-in Deferreds.
+Always use this argument unless you are adding further callbacks or errbacks to
+the passed-in Deferreds, or unless you know that they will not fail.
+Otherwise, a failure will result in an unhandled error being logged by Twisted.
+This argument is available since Twisted 11.1.0.</p>
+
+<a name="class" shape="rect"/>
+
+<h2>Class Overview<a name="auto11"/></h2>
+
+<p>This is an overview API reference for Deferred from the point of using a
+Deferred returned by a function. It is not meant to be a
+substitute for the docstrings in the Deferred class, but can provide guidelines
+for its use.</p>
+
+<p>There is a parallel overview of functions used by the Deferred's <em>creator</em> in <a href="gendefer.html#class" shape="rect">Generating Deferreds</a>.</p>
+
+<h3>Basic Callback Functions<a name="auto12"/></h3>
+
+<ul>
+ <li>
+ <code class="py-prototype">addCallbacks(self, callback[, errback, callbackArgs,
+ callbackKeywords, errbackArgs, errbackKeywords])</code>
+
+ <p>This is the method you will use to interact
+ with Deferred. It adds a pair of callbacks <q>parallel</q> to
+ each other (see diagram above) in the list of callbacks
+ made when the Deferred is called back to. The signature of
+ a method added using addCallbacks should be
+ <code>myMethod(result, *methodArgs,
+ **methodKeywords)</code>. If your method is passed in the
+ callback slot, for example, all arguments in the tuple
+ <code>callbackArgs</code> will be passed as
+ <code>*methodArgs</code> to your method.</p>
+
+ <p>There are various convenience methods that are
+ derivative of addCallbacks. I will not cover them in detail
+ here, but it is important to know about them in order to
+ create concise code.</p>
+
+ <ul>
+ <li>
+ <code class="py-prototype">addCallback(callback, *callbackArgs,
+ **callbackKeywords)</code>
+
+ <p>Adds your callback at the next point in the
+ processing chain, while adding an errback that will
+ re-raise its first argument, not affecting further
+ processing in the error case.</p>
+
+ <p>Note that, while addCallbacks (plural) requires the arguments to be
+ passed in a tuple, addCallback (singular) takes all its remaining
+ arguments as things to be passed to the callback function. The reason is
+ obvious: addCallbacks (plural) cannot tell whether the arguments are
+ meant for the callback or the errback, so they must be specifically
+ marked by putting them into a tuple. addCallback (singular) knows that
+ everything is destined to go to the callback, so it can use Python's
+ <q>*</q> and <q>**</q> syntax to collect the remaining arguments.</p>
+
+ </li>
+
+ <li>
+ <code class="py-prototype">addErrback(errback, *errbackArgs,
+ **errbackKeywords)</code>
+
+ <p>Adds your errback at the next point in the
+ processing chain, while adding a callback that will
+ return its first argument, not affecting further
+ processing in the success case.</p>
+ </li>
+
+ <li>
+ <code class="py-prototype">addBoth(callbackOrErrback,
+ *callbackOrErrbackArgs,
+ **callbackOrErrbackKeywords)</code>
+
+ <p>This method adds the same callback into both sides
+ of the processing chain at both points. Keep in mind
+ that the type of the first argument is indeterminate if
+ you use this method! Use it for <code>finally:</code>
+ style blocks.</p>
+ </li>
+ </ul> </li>
+
+</ul>
+
+
+<h3>Chaining Deferreds<a name="auto13"/></h3>
+
+<p>If you need one Deferred to wait on another, all you need to do is return a
+Deferred from a method added to addCallbacks. Specifically, if you return
+Deferred B from a method added to Deferred A using A.addCallbacks, Deferred A's
+processing chain will stop until Deferred B's .callback() method is called; at
+that point, the next callback in A will be passed the result of the last
+callback in Deferred B's processing chain at the time.</p>
+
+<p>If this seems confusing, don't worry about it right now -- when you run into
+a situation where you need this behavior, you will probably recognize it
+immediately and realize why this happens. If you want to chain deferreds
+manually, there is also a convenience method to help you.</p>
+
+<ul>
+ <li>
+ <code class="py-prototype">chainDeferred(otherDeferred)</code>
+
+ <p>Add <code>otherDeferred</code> to the end of this
+ Deferred's processing chain. When self.callback is called,
+ the result of my processing chain up to this point will be
+ passed to <code>otherDeferred.callback</code>. Further
+ additions to my callback chain do not affect
+ <code>otherDeferred</code></p>
+ <p>This is the same as <code class="python">self.addCallbacks(otherDeferred.callback,
+ otherDeferred.errback)</code></p>
+ </li>
+</ul>
+
+<h2>See also<a name="auto14"/></h2>
+
+<ol>
+<li><a href="gendefer.html" shape="rect">Generating Deferreds</a>, an introduction to
+writing asynchronous functions that return Deferreds.</li>
+</ol>
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">Unless of course a later callback starts a fresh error —
+but as we've already noted, adding callbacks to a Deferred after its used in a
+DeferredList is confusing and usually avoided.</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/design.html b/doc/core/howto/design.html
new file mode 100644
index 0000000..49e292c
--- /dev/null
+++ b/doc/core/howto/design.html
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Designing Twisted Applications</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Designing Twisted Applications</h1>
+ <div class="toc"><ol><li><a href="#auto0">Goals</a></li><li><a href="#auto1">Example of a modular design: TwistedQuotes</a></li><ul><li><a href="#auto2">Set up the project directory</a></li><li><a href="#auto3">A Look at the Heart of the Application</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Goals<a name="auto0"/></h2>
+
+<p>This document describes how a good Twisted application is structured. It
+should be useful for beginning Twisted developers who want to structure their
+code in a clean, maintainable way that reflects current best practices.</p>
+
+<p>Readers will want to be familiar with writing <a href="servers.html" shape="rect">servers</a> and <a href="clients.html" shape="rect">clients</a> using Twisted.</p>
+
+<h2>Example of a modular design: TwistedQuotes<a name="auto1"/></h2>
+
+<p><code>TwistedQuotes</code> is a very simple plugin which is a great
+demonstration of
+Twisted's power. It will export a small kernel of functionality -- Quote of
+the Day -- which can be accessed through every interface that Twisted supports:
+web pages, e-mail, instant messaging, a specific Quote of the Day protocol, and
+more.</p>
+
+<h3>Set up the project directory<a name="auto2"/></h3>
+
+<p>See the description of <a href="quotes.html" shape="rect">setting up the TwistedQuotes
+example</a>.</p>
+
+<h3>A Look at the Heart of the Application<a name="auto3"/></h3>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">random</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">choice</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">TwistedQuotes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">quoteproto</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">StaticQuoter</span>:
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a static quote.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">quoteproto</span>.<span class="py-src-variable">IQuoter</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">quote</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">quote</span> = <span class="py-src-variable">quote</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">quote</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FortuneQuoter</span>:
+ <span class="py-src-string">&quot;&quot;&quot;
+ Load quotes from a fortune-format file.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">quoteproto</span>.<span class="py-src-variable">IQuoter</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filenames</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filenames</span> = <span class="py-src-variable">filenames</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">quoteFile</span> = <span class="py-src-variable">file</span>(<span class="py-src-variable">choice</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filenames</span>))
+ <span class="py-src-variable">quotes</span> = <span class="py-src-variable">quoteFile</span>.<span class="py-src-variable">read</span>().<span class="py-src-variable">split</span>(<span class="py-src-string">'\n%\n'</span>)
+ <span class="py-src-variable">quoteFile</span>.<span class="py-src-variable">close</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">choice</span>(<span class="py-src-variable">quotes</span>)
+</pre><div class="caption">Twisted Quotes
+Central Abstraction - <a href="listings/TwistedQuotes/quoters.py"><span class="filename">listings/TwistedQuotes/quoters.py</span></a></div></div>
+
+<p>This code listing shows us what the Twisted Quotes system is all about. The
+code doesn't have any way of talking to the outside world, but it provides a
+library which is a clear and uncluttered abstraction: <q>give me the quote of
+the day</q>. </p>
+
+<p>Note that this module does not import any Twisted functionality at all! The
+reason for doing things this way is integration. If your <q>business
+objects</q> are not stuck to your user interface, you can make a module that
+can integrate those objects with different protocols, GUIs, and file formats.
+Having such classes provides a way to decouple your components from each other,
+by allowing each to be used independently.</p>
+
+<p>In this manner, Twisted itself has minimal impact on the logic of your
+program. Although the Twisted <q>dot products</q> are highly interoperable,
+they
+also follow this approach. You can use them independently because they are not
+stuck to each other. They communicate in well-defined ways, and only when that
+communication provides some additional feature. Thus, you can use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.html" title="twisted.web">twisted.web</a></code> with <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.enterprise.html" title="twisted.enterprise">twisted.enterprise</a></code>, but neither requires the other, because
+they are integrated around the concept of <a href="defer.html" shape="rect">Deferreds</a>.</p>
+
+<p>Your Twisted applications should follow this style as much as possible.
+Have (at least) one module which implements your specific functionality,
+independent of any user-interface code. </p>
+
+<p>Next, we're going to need to associate this abstract logic with some way of
+displaying it to the user. We'll do this by writing a Twisted server protocol,
+which will respond to the clients that connect to it by sending a quote to the
+client and then closing the connection. Note: don't get too focused on the
+details of this -- different ways to interface with the user are 90% of what
+Twisted does, and there are lots of documents describing the different ways to
+do it.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IQuoter</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ An object that returns quotes.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a quote.
+ &quot;&quot;&quot;</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">quoter</span>.<span class="py-src-variable">getQuote</span>()+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTDFactory</span>(<span class="py-src-parameter">Factory</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ A factory for the Quote of the Day protocol.
+
+ @type quoter: L{IQuoter} provider
+ @ivar quoter: An object which provides L{IQuoter} which will be used by
+ the L{QOTD} protocol to get quotes to emit.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">QOTD</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">quoter</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">quoter</span> = <span class="py-src-variable">quoter</span>
+</pre><div class="caption">Twisted
+Quotes Protocol Implementation - <a href="listings/TwistedQuotes/quoteproto.py"><span class="filename">listings/TwistedQuotes/quoteproto.py</span></a></div></div>
+
+<p>This is a very straightforward <code>Protocol</code> implementation, and the
+pattern described above is repeated here. The Protocol contains essentially no
+logic of its own, just enough to tie together an object which can generate
+quotes (a <code class="python">Quoter</code>) and an object which can relay
+bytes to a TCP connection (a <code class="python">Transport</code>). When a
+client connects to this server, a <code class="python">QOTD</code> instance is
+created, and its <code class="python">connectionMade</code> method is called.
+</p>
+
+<p> The <code class="python">QOTDFactory</code>'s role is to specify to the
+Twisted framework how to create a <code class="python">Protocol</code> instance
+that will handle the connection. Twisted will not instantiate a <code class="python">QOTDFactory</code>; you will do that yourself later, in a <code class="shell">twistd</code> plug-in.
+</p>
+
+<p>Note: you can read more specifics of <code class="python">Protocol</code> and <code class="python">Factory</code> in the <a href="servers.html" shape="rect">Writing
+Servers</a> HOWTO.</p>
+
+<p>Once we have an abstraction -- a <code>Quoter</code> -- and we have a
+mechanism to connect it to the network -- the <code>QOTD</code> protocol -- the
+next thing to do is to put the last link in the chain of functionality between
+abstraction and user. This last link will allow a user to choose a <code>Quoter</code> and configure the protocol. Writing this configuration is
+covered in the <a href="application.html" shape="rect">Application HOWTO</a>.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/dirdbm.html b/doc/core/howto/dirdbm.html
new file mode 100644
index 0000000..f2b86b2
--- /dev/null
+++ b/doc/core/howto/dirdbm.html
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: DirDBM: Directory-based Storage</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">DirDBM: Directory-based Storage</h1>
+ <div class="toc"><ol><li><a href="#auto0">dirdbm.DirDBM</a></li><li><a href="#auto1">dirdbm.Shelf</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>dirdbm.DirDBM<a name="auto0"/></h2>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.persisted.dirdbm.DirDBM.html" title="twisted.persisted.dirdbm.DirDBM">twisted.persisted.dirdbm.DirDBM</a></code> is a DBM-like storage system.
+That is, it stores mappings between keys
+and values, like a Python dictionary, except that it stores the values in files
+in a directory - each entry is a different file. The keys must always be strings,
+as are the values. Other than that, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.persisted.dirdbm.DirDBM.html" title="twisted.persisted.dirdbm.DirDBM">DirDBM</a></code>
+objects act just like Python dictionaries.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.persisted.dirdbm.DirDBM.html" title="twisted.persisted.dirdbm.DirDBM">DirDBM</a></code> is useful for cases
+when you want to store small amounts of data in an organized fashion, without having
+to deal with the complexity of a RDBMS or other sophisticated database. It is simple,
+easy to use, cross-platform, and doesn't require any external C libraries, unlike
+Python's built-in DBM modules.</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; from twisted.persisted import dirdbm
+&gt;&gt;&gt; d = dirdbm.DirDBM(&quot;/tmp/dir&quot;)
+&gt;&gt;&gt; d[&quot;librarian&quot;] = &quot;ook&quot;
+&gt;&gt;&gt; d[&quot;librarian&quot;]
+'ook'
+&gt;&gt;&gt; d.keys()
+['librarian']
+&gt;&gt;&gt; del d[&quot;librarian&quot;]
+&gt;&gt;&gt; d.items()
+[]
+</pre>
+
+<h2>dirdbm.Shelf<a name="auto1"/></h2>
+
+<p>Sometimes it is neccessary to persist more complicated objects than strings.
+With some care, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.persisted.dirdbm.Shelf.html" title="twisted.persisted.dirdbm.Shelf">dirdbm.Shelf</a></code>
+can transparently persist
+them. <code>Shelf</code> works exactly like <code>DirDBM</code>, except that
+the values (but not the keys) can be arbitrary picklable objects. However,
+notice that mutating an object after it has been stored in the <code>Shelf</code> has no effect on the Shelf.
+When mutating objects, it is neccessary to explictly store them back in the <code>Shelf</code>
+afterwards:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; from twisted.persisted import dirdbm
+&gt;&gt;&gt; d = dirdbm.Shelf(&quot;/tmp/dir2&quot;)
+&gt;&gt;&gt; d[&quot;key&quot;] = [1, 2]
+&gt;&gt;&gt; d[&quot;key&quot;]
+[1, 2]
+&gt;&gt;&gt; l = d[&quot;key&quot;]
+&gt;&gt;&gt; l.append(3)
+&gt;&gt;&gt; d[&quot;key&quot;]
+[1, 2]
+&gt;&gt;&gt; d[&quot;key&quot;] = l
+&gt;&gt;&gt; d[&quot;key&quot;]
+[1, 2, 3]
+</pre>
+
+
+
+
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/endpoints.html b/doc/core/howto/endpoints.html
new file mode 100644
index 0000000..333a3fd
--- /dev/null
+++ b/doc/core/howto/endpoints.html
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Getting Connected with Endpoints</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Getting Connected with Endpoints</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Constructing and Using Endpoints</a></li><ul><li><a href="#auto2">There's Not Much To It</a></li><li><a href="#auto3">Servers and Stopping</a></li><li><a href="#auto4">Clients and Cancelling</a></li></ul><li><a href="#auto5">Maximizing the Return on your Endpoint Investment</a></li><ul><li><a href="#auto6">Endpoints Aren't Always the Answer</a></li></ul><li><a href="#auto7">Endpoint Types Included With Twisted</a></li><ul><li><a href="#auto8">Clients</a></li><li><a href="#auto9">Servers</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p>On a network, one can think of any given connection as a long wire,
+stretched between two points. Lots of stuff can happen along the length of
+that wire - routers, switches, network address translation, and so on, but
+that is usually invisible to the application passing data across it.
+Twisted strives to make the nature of the &quot;wire&quot; as transparent as
+possible, with highly abstract interfaces for passing and receiving data,
+such as <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.ITransport.html" title="twisted.internet.interfaces.ITransport">ITransport</a></code>
+and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProtocol.html" title="twisted.internet.interfaces.IProtocol">IProtocol</a></code>.</p>
+
+<p>However, the application can't be completely ignorant of the wire.
+In particular, it must do something to <em>start</em> the connection, and
+to do so, it must identify the <em>end points</em> of the wire. There are
+different names for the roles of each end point - &quot;initiator&quot; and
+&quot;responder&quot;, &quot;connector&quot; and &quot;listener&quot;, or &quot;client&quot; and &quot;server&quot; - but the
+common theme is that one side of the connection waits around for someone to
+connect to it, and the other side does the connecting.</p>
+
+<p>In Twisted 10.1, several new interfaces were introduced to describe
+each of these roles for stream-oriented connections: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IStreamServerEndpoint.html" title="twisted.internet.interfaces.IStreamServerEndpoint">IStreamServerEndpoint</a></code> and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IStreamClientEndpoint.html" title="twisted.internet.interfaces.IStreamClientEndpoint">IStreamClientEndpoint</a></code>.
+The word &quot;stream&quot;, in this case, refers to endpoints which treat a
+connection as a continuous stream of bytes, rather than a sequence of
+discrete datagrams: TCP is a &quot;stream&quot; protocol whereas UDP is a &quot;datagram&quot;
+protocol.</p>
+
+<h2>Constructing and Using Endpoints<a name="auto1"/></h2>
+
+<p>In both <a href="servers.html" shape="rect">Writing Servers</a> and <a href="clients.html" shape="rect">Writing Clients</a>, we covered basic usage of
+endpoints; you construct an appropriate type of server or client endpoint,
+and then call <code>listen</code> (for servers) or <code>connect</code>
+(for clients).</p>
+
+<p>In both of those tutorials, we constructed specific types of
+endpoints directly. However, in most programs, you will want to allow the
+user to specify where to listen or connect, in a way which will allow the
+user to request different strategies, without having to adjust your
+program. In order to allow this, you should use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.endpoints.clientFromString.html" title="twisted.internet.endpoints.clientFromString">clientFromString</a></code> or <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.endpoints.serverFromString.html" title="twisted.internet.endpoints.serverFromString">serverFromString</a></code>.</p>
+
+<h3>There's Not Much To It<a name="auto2"/></h3>
+
+<p>Each type of endpoint is just an interface with a single method that
+takes an argument. <code>serverEndpoint.listen(factory)</code> will start
+listening on that endpoint with your protocol factory, and
+<code>clientEndpoint.connect(factory)</code> will start a single connection
+attempt. Each of these APIs returns a value, though, which can be important.
+</p>
+
+<p>However, if you are not already, you <em>should</em> be very
+familiar with <a href="defer.html" shape="rect">Deferreds</a>, as they are returned by
+both <code>connect</code> and <code>listen</code> methods, to indicate when
+the connection has connected or the listening port is up and running.</p>
+
+<h3>Servers and Stopping<a name="auto3"/></h3>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IStreamServerEndpoint.listen.html" title="twisted.internet.interfaces.IStreamServerEndpoint.listen">IStreamServerEndpoint.listen</a></code>
+returns a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>
+that fires with an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IListeningPort.html" title="twisted.internet.interfaces.IListeningPort">IListeningPort</a></code>.
+Note that this deferred may errback. The most common cause of such an error
+would be that another program is already using the requested port number,
+but the exact cause may vary depending on what type of endpoint you are
+listening on. If you receive such an error, it means that your application
+is not actually listening, and will not receive any incoming connections.
+It's important to somehow alert an administrator of your server, in this
+case, especially if you only have one listening port!</p>
+
+<p>Note also that once this has succeeded, it will continue listening
+forever. If you need to <em>stop</em> listening for some reason, in
+response to anything other than a full server shutdown (<code>reactor.stop</code>
+and / or <code>twistd</code> will usually handle that case for you), make
+sure you keep a reference around to that listening port object so you can
+call <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IListeningPort.stopListening.html" title="twisted.internet.interfaces.IListeningPort.stopListening">IListeningPort.stopListening</a></code>
+on it. Finally, keep in mind that <code>stopListening</code> itself returns
+a <code>Deferred</code>, and the port may not have fully stopped listening
+until that <code>Deferred</code> has fired.</p>
+
+<p>Most server applications will not need to worry about these details.
+One example of a case where you would need to be concerned with all of
+these events would be an implementation of a protocol like non-<code>PASV</code>
+FTP, where new listening ports need to be bound for the lifetime of a
+particular action, then disposed of.</p>
+
+<h3>Clients and Cancelling<a name="auto4"/></h3>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IStreamClientEndpoint.connect.html" title="twisted.internet.interfaces.IStreamClientEndpoint.connect">IStreamClientEndpoint.connect</a></code>
+will connect your protocol factory to a new outgoing connection attempt. It
+returns a <code>Deferred</code> which fires with the <code>IProtocol</code>
+returned from the factory's <code>buildProtocol</code> method.</p>
+
+<p>Connection attempts may fail, and so that <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> may also errback. If it does so,
+you will have to try again; your protocol won't be constructed, and no further
+attempts will be made.</p>
+
+<p>Connection attempts may also take a long time, and your users may
+become bored and wander off. If this happens, and your code decides, for
+whatever reason, that you've been waiting for the connection too long, you
+can call <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.cancel.html" title="twisted.internet.defer.Deferred.cancel">Deferred.cancel</a></code>
+on the <code>Deferred</code> returned from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IClientStreamEndpoint.connect.html" title="twisted.internet.interfaces.IClientStreamEndpoint.connect">connect</a></code>, and the
+underlying machinery should give up on the connection. This should cause the
+<code>Deferred</code> to errback, usually with <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.CancelledError.html" title="twisted.internet.defer.CancelledError">CancelledError</a></code>; although you should
+consult the documentation for your particular endpoint type to see if it may do
+something different.</p>
+
+<p>Although some endpoint types may imply a built-in timeout, the
+interface does not guarantee one. If you don't have any way for the
+application to cancel a wayward connection attempt, the attempt may just
+keep waiting forever. For example, a very simple 30-second timeout could be
+implemented like this:
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">attempt</span> = <span class="py-src-variable">myEndpoint</span>.<span class="py-src-variable">connect</span>(<span class="py-src-variable">myFactory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">attempt</span>.<span class="py-src-variable">cancel</span>)
+</pre>
+</p>
+
+<h2>Maximizing the Return on your Endpoint Investment<a name="auto5"/></h2>
+
+<p>Directly constructing an endpoint in your application is rarely the
+best option, because it ties your application to a particular type of
+transport. The strength of the endpoints API is in separating the
+construction of the endpoint (figuring out where to connect or listen) and
+its activation (actually connecting or listening).</p>
+
+<p>If you are implementing a library that needs to listen for
+connections or make outgoing connections, when possible, you should write
+your code to accept client and server endpoints as parameters to functions
+or to your objects' constructors. That way, application code that calls
+your library can provide whatever endpoints are appropriate.</p>
+
+<p>If you are writing an application and you need to construct
+endpoints yourself, you can allow users to specify arbitrary endpoints
+described by a string using the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.endpoints.clientFromString.html" title="twisted.internet.endpoints.clientFromString">clientFromString</a></code> and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.endpoints.serverFromString.html" title="twisted.internet.endpoints.serverFromString">serverFromString</a></code>
+APIs. Since these APIs just take a string, they provide flexibility: if
+Twisted adds support for new types of endpoints (for example, IPv6
+endpoints, or WebSocket endpoints), your application will automatically be
+able to take advantage of them with no changes to its code.</p>
+
+<h3>Endpoints Aren't Always the Answer<a name="auto6"/></h3>
+
+<p>For many use-cases, especially the common case of a <code>twistd</code>
+plugin which runs a long-running server that just binds a simple port, you
+might not want to use the endpoints APIs directly. Instead, you may want to
+construct an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code>, using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.strports.service.html" title="twisted.application.strports.service">strports.service</a></code> , which will fit
+neatly into the required structure of <a href="plugin.html" shape="rect">the twistd
+plugin API</a> . This doesn't give your application much control - the port
+starts listening at startup and stops listening at shutdown - but it does
+provide the same flexibility in terms of what type of server endpoint your
+application will support.</p>
+
+<p>It is, however, almost always preferable to use an endpoint rather
+than calling a lower-level APIs like <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTCP.connectTCP.html" title="twisted.internet.interfaces.IReactorTCP.connectTCP">connectTCP</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTCP.listenTCP.html" title="twisted.internet.interfaces.IReactorTCP.listenTCP">listenTCP</a></code>,
+etc, directly. By accepting an arbitrary endpoint rather than requiring a
+specific reactor interface, you leave your application open to lots of
+interesting transport-layer extensibility for the future.</p>
+
+<h2>Endpoint Types Included With Twisted<a name="auto7"/></h2>
+
+<p>The parser used by <code>clientFromString</code> and
+<code>serverFromString</code> is extensible via third-party plugins, so the
+endpoints available on your system depend on what packages you have installed.
+However, Twisted itself includes a set of basic endpoints that will always be
+available.</p>
+
+<h3>Clients<a name="auto8"/></h3>
+
+<ul>
+ <li>TCP. Supported arguments: host, port, timeout. timeout is optional. For
+ example, <code>tcp:host=twistedmatrix.com:port=80:timeout=15</code>.
+ </li>
+ <li>SSL. All TCP arguments are supported, plus: certKey, privateKey,
+ caCertsDir. certKey (optional) gives a filesystem path to a certificate (PEM
+ format). privateKey (optional) gives a filesystem path to a a private key
+ (PEM format). caCertsDir (optional) gives a filesystem path to a directory
+ containing trusted CA certificates to use to verify the server certificate.
+ For example,
+ <code>ssl:host=twistedmatrix.com:port=443:caCertsDir=/etc/ssl/certs</code>.
+ </li>
+ <li>UNIX. Supported arguments: path, timeout, checkPID. path gives a
+ filesystem path to a listening UNIX domain socket server. checkPID (optional)
+ enables a check of the lock file Twisted-based UNIX domain socket servers use
+ to prove they are still running. For
+ example, <code>unix:path=/var/run/web.sock</code>.
+ </li>
+</ul>
+
+<h3>Servers<a name="auto9"/></h3>
+
+<ul>
+ <li>TCP. Supported arguments: port, interface, backlog. interface and
+ backlog are optional. interface is an IP address to bind to. For example,
+ <code>tcp:port=80:interface=192.168.1.1</code>.
+ </li>
+ <li>SSL. All TCP arguments are supported, plus: certKey, privateKey, and
+ sslmethod. certKey (optional, defaults to the value of privateKey) gives a
+ filesystem path to a certificate (PEM format). privateKey gives a filesystem
+ path to a a private key (PEM format). sslmethod indicates which SSL/TLS
+ version to use (a value like TLSv1_METHOD). For example,
+ <code>ssl:port=443:privateKey=/etc/ssl/server.pem:sslmethod=SSLv3_METHOD</code>.
+ </li>
+ <li>UNIX. Supported arguments: address, mode, backlog, lockfile. address
+ gives a filesystem path to listen on with a UNIX domain socket server. mode
+ (optional) gives the filesystem permission/mode (in octal) to apply to that
+ socket. lockfile enables use of a separate lock file to prove the server is
+ still running. For example, <code>unix:address=/var/run/web.sock:lockfile=1</code>.
+ </li>
+ <li>systemd. Supported arguments: domain, index. domain indicates which
+ socket domain the inherited file descriptor belongs to (eg INET, INET6).
+ index indicates an offset into the array of file descriptors which have been
+ inherited from systemd. For
+ example, <code>systemd:domain=INET6:index=3</code>.
+ </li>
+</ul>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/gendefer.html b/doc/core/howto/gendefer.html
new file mode 100644
index 0000000..44b35b9
--- /dev/null
+++ b/doc/core/howto/gendefer.html
@@ -0,0 +1,411 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Generating Deferreds</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Generating Deferreds</h1>
+ <div class="toc"><ol><li><a href="#auto0">Class overview</a></li><ul><li><a href="#auto1">Basic Callback Functions</a></li></ul><li><a href="#auto2">What Deferreds don't do: make your code asynchronous</a></li><li><a href="#auto3">Advanced Processing Chain Control</a></li><li><a href="#auto4">Returning Deferreds from synchronous functions</a></li><li><a href="#auto5">Integrating blocking code with Twisted</a></li><li><a href="#auto6">Possible sources of error</a></li><ul><li><a href="#auto7">Firing Deferreds more than once is impossible</a></li><li><a href="#auto8">Synchronous callback execution</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> objects are
+signals that a function you have called does not yet have the data you want
+available. When a function returns a Deferred object, your calling function
+attaches callbacks to it to handle the data when available.</p>
+
+<p>This document addresses the other half of the question: writing functions
+that return Deferreds, that is, constructing Deferred objects, arranging for
+them to be returned immediately without blocking until data is available, and
+firing their callbacks when the data is available.</p>
+
+<p>This document assumes that you are familiar with the asynchronous model used
+by Twisted, and with <a href="defer.html" shape="rect">using deferreds returned by functions</a>
+.</p>
+
+<a name="class" shape="rect"/>
+
+<h2>Class overview<a name="auto0"/></h2>
+
+<p>This is an overview API reference for Deferred from the point of creating a
+Deferred and firing its callbacks and errbacks. It is not meant to be a
+substitute for the docstrings in the Deferred class, but can provide
+guidelines for its use.</p>
+
+<p>There is a parallel overview of functions used by calling function which
+the Deferred is returned to at <a href="defer.html#class" shape="rect">Using Deferreds</a>.</p>
+
+<h3>Basic Callback Functions<a name="auto1"/></h3>
+
+<ul>
+ <li>
+ <code class="py-prototype">callback(result)</code>
+
+ <p>Run success callbacks with the given result. <em>This
+ can only be run once.</em> Later calls to this or
+ <code>errback</code> will raise <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.AlreadyCalledError.html" title="twisted.internet.defer.AlreadyCalledError">twisted.internet.defer.AlreadyCalledError</a></code>.
+ If further callbacks or errbacks are added after this
+ point, addCallbacks will run the callbacks immediately.</p>
+ </li>
+
+ <li>
+ <code class="py-prototype">errback(failure)</code>
+
+ <p>Run error callbacks with the given failure. <em>This can
+ only be run once.</em> Later calls to this or
+ <code>callback</code> will raise <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.AlreadyCalledError.html" title="twisted.internet.defer.AlreadyCalledError">twisted.internet.defer.AlreadyCalledError</a></code>.
+ If further callbacks or errbacks are added after this
+ point, addCallbacks will run the callbacks immediately.</p>
+ </li>
+</ul>
+
+<h2>What Deferreds don't do: make your code asynchronous<a name="auto2"/></h2>
+
+<p><em>Deferreds do not make the code magically not block.</em></p>
+
+<p>Let's take this function as an example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+<span class="py-src-variable">TARGET</span> = <span class="py-src-number">10000</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">largeFibonnaciNumber</span>():
+ <span class="py-src-comment"># create a Deferred object to return:</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+
+ <span class="py-src-comment"># calculate the ten thousandth Fibonnaci number</span>
+
+ <span class="py-src-variable">first</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">second</span> = <span class="py-src-number">1</span>
+
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">i</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">xrange</span>(<span class="py-src-variable">TARGET</span> - <span class="py-src-number">1</span>):
+ <span class="py-src-variable">new</span> = <span class="py-src-variable">first</span> + <span class="py-src-variable">second</span>
+ <span class="py-src-variable">first</span> = <span class="py-src-variable">second</span>
+ <span class="py-src-variable">second</span> = <span class="py-src-variable">new</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">i</span> % <span class="py-src-number">100</span> == <span class="py-src-number">0</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Progress: calculating the %dth Fibonnaci number&quot;</span> % <span class="py-src-variable">i</span>
+
+ <span class="py-src-comment"># give the Deferred the answer to pass to the callbacks:</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">second</span>)
+
+ <span class="py-src-comment"># return the Deferred with the answer:</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+
+<span class="py-src-variable">timeBefore</span> = <span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>()
+
+<span class="py-src-comment"># call the function and get our Deferred</span>
+<span class="py-src-variable">d</span> = <span class="py-src-variable">largeFibonnaciNumber</span>()
+
+<span class="py-src-variable">timeAfter</span> = <span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>()
+
+<span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Total time taken for largeFibonnaciNumber call: %0.3f seconds&quot;</span> %
+ (<span class="py-src-variable">timeAfter</span> - <span class="py-src-variable">timeBefore</span>)
+
+<span class="py-src-comment"># add a callback to it to print the number</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printNumber</span>(<span class="py-src-parameter">number</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;The %dth Fibonacci number is %d&quot;</span> % (<span class="py-src-variable">TARGET</span>, <span class="py-src-variable">number</span>)
+
+<span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Adding the callback now.&quot;</span>
+
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printNumber</span>)
+</pre>
+
+<p>You will notice that despite creating a Deferred in the <code>largeFibonnaciNumber</code> function, these things happened:</p>
+<ul>
+<li>the &quot;Total time taken for largeFibonnaciNumber call&quot; output
+shows that the function did not return immediately as asynchronous functions
+are expected to do; and</li>
+<li>rather than the callback being added before the result was available and
+called after the result is available, it isn't even added until after the
+calculation has been completed.</li>
+</ul>
+
+<p> The function completed its calculation before returning, blocking the
+process until it had finished, which is exactly what asynchronous functions
+are not meant to do. Deferreds are not a non-blocking talisman: they are a
+signal for asynchronous functions to <em>use</em> to pass results onto
+callbacks, but using them does not guarantee that you have an asynchronous
+function.</p>
+
+
+<h2>Advanced Processing Chain Control<a name="auto3"/></h2>
+
+<ul>
+ <li>
+ <code class="py-prototype">pause()</code>
+
+ <p>Cease calling any methods as they are added, and do not
+ respond to <code>callback</code>, until
+ <code>self.unpause()</code> is called.</p>
+ </li>
+
+ <li>
+ <code class="py-prototype">unpause()</code>
+
+ <p>If <code>callback</code> has been called on this
+ Deferred already, call all the callbacks that have been
+ added to this Deferred since <code>pause</code> was
+ called.</p>
+
+ <p>Whether it was called or not, this will put this
+ Deferred in a state where further calls to
+ <code>addCallbacks</code> or <code>callback</code> will
+ work as normal.</p>
+ </li>
+</ul>
+
+<h2>Returning Deferreds from synchronous functions<a name="auto4"/></h2>
+
+<p>Sometimes you might wish to return a Deferred from a synchronous function.
+There are several reasons why, the major two are maintaining API compatibility
+with another version of your function which returns a Deferred, or allowing
+for the possiblity that in the future your function might need to be
+asynchronous.</p>
+
+<p>In the <a href="defer.html" shape="rect">Using Deferreds</a> reference, we gave the
+following example of a synchronous function:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">synchronousIsValidUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">'''
+ Return true if user is a valid user, false otherwise
+ '''</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">&quot;Alice&quot;</span>, <span class="py-src-string">&quot;Angus&quot;</span>, <span class="py-src-string">&quot;Agnes&quot;</span>]
+</pre><div class="caption">Source listing - <a href="listings/deferred/synch-validation.py"><span class="filename">listings/deferred/synch-validation.py</span></a></div></div>
+
+<p>While we can require that callers of our function wrap our synchronous
+result in a Deferred using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.maybeDeferred.html" title="twisted.internet.defer.maybeDeferred">maybeDeferred</a></code>, for the sake of API
+compatibility it is better to return a Deferred ourselves using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.succeed.html" title="twisted.internet.defer.succeed">defer.succeed</a></code>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">immediateIsValidUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">'''
+ Returns a Deferred resulting in true if user is a valid user, false
+ otherwise
+ '''</span>
+
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">&quot;Alice&quot;</span>, <span class="py-src-string">&quot;Angus&quot;</span>, <span class="py-src-string">&quot;Agnes&quot;</span>]
+
+ <span class="py-src-comment"># return a Deferred object already called back with the value of result</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">result</span>)
+</pre>
+
+<p>There is an equivalent <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.fail.html" title="twisted.internet.defer.fail">defer.fail</a></code> method to return a Deferred with the
+errback chain already fired.</p>
+
+<h2>Integrating blocking code with Twisted<a name="auto5"/></h2>
+
+<p>At some point, you are likely to need to call a blocking function: many
+functions in third party libraries will have long running blocking functions.
+There is no way to 'force' a function to be asynchronous: it must be written
+that way specifically. When using Twisted, your own code should be
+asynchronous, but there is no way to make third party functions asynchronous
+other than rewriting them.</p>
+
+<p>In this case, Twisted provides the ability to run the blocking code in a
+separate thread rather than letting it block your application. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.threads.deferToThread.html" title="twisted.internet.threads.deferToThread">twisted.internet.threads.deferToThread</a></code> function will set up
+a thread to run your blocking function, return a Deferred and later fire that
+Deferred when the thread completes.</p>
+
+<p>Let's assume our <code class="python">largeFibonnaciNumber</code> function
+from above is in a third party library (returning the result of the
+calculation, not a Deferred) and is not easily modifiable to be finished in
+discrete blocks. This example shows it being called in a thread, unlike in the
+earlier section we'll see that the operation does not block our entire
+program:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">largeFibonnaciNumber</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Represent a long running blocking function by calculating
+ the TARGETth Fibonnaci number
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">TARGET</span> = <span class="py-src-number">10000</span>
+
+ <span class="py-src-variable">first</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">second</span> = <span class="py-src-number">1</span>
+
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">i</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">xrange</span>(<span class="py-src-variable">TARGET</span> - <span class="py-src-number">1</span>):
+ <span class="py-src-variable">new</span> = <span class="py-src-variable">first</span> + <span class="py-src-variable">second</span>
+ <span class="py-src-variable">first</span> = <span class="py-src-variable">second</span>
+ <span class="py-src-variable">second</span> = <span class="py-src-variable">new</span>
+
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">second</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">threads</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">fibonacciCallback</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Callback which manages the largeFibonnaciNumber result by
+ printing it out
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;largeFibonnaciNumber result =&quot;</span>, <span class="py-src-variable">result</span>
+ <span class="py-src-comment"># make sure the reactor stops after the callback chain finishes,</span>
+ <span class="py-src-comment"># just so that this example terminates</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">run</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Run a series of operations, deferring the largeFibonnaciNumber
+ operation to a thread and performing some other operations after
+ adding the callback
+ &quot;&quot;&quot;</span>
+ <span class="py-src-comment"># get our Deferred which will be called with the largeFibonnaciNumber result</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">threads</span>.<span class="py-src-variable">deferToThread</span>(<span class="py-src-variable">largeFibonnaciNumber</span>)
+ <span class="py-src-comment"># add our callback to print it out</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">fibonacciCallback</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;1st line after the addition of the callback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;2nd line after the addition of the callback&quot;</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">run</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h2>Possible sources of error<a name="auto6"/></h2>
+
+<p>Deferreds greatly simplify the process of writing asynchronous code by
+providing a standard for registering callbacks, but there are some subtle and
+sometimes confusing rules that you need to follow if you are going to use
+them. This mostly applies to people who are writing new systems that use
+Deferreds internally, and not writers of applications that just add callbacks
+to Deferreds produced and processed by other systems. Nevertheless, it is good
+to know.</p>
+
+<h3>Firing Deferreds more than once is impossible<a name="auto7"/></h3>
+
+<p>Deferreds are one-shot. You can only call <code>Deferred.callback</code> or <code>Deferred.errback</code> once. The processing chain continues each time
+you add new callbacks to an already-called-back-to Deferred.</p>
+
+<h3>Synchronous callback execution<a name="auto8"/></h3>
+
+<p>If a Deferred already has a result available, <code>addCallback</code> <strong>may</strong> call the callback synchronously: that is, immediately
+after it's been added. In situations where callbacks modify state, it is
+might be desirable for the chain of processing to halt until all callbacks are
+added. For this, it is possible to <code>pause</code> and <code>unpause</code>
+a Deferred's processing chain while you are adding lots of callbacks.</p>
+
+<p>Be careful when you use these methods! If you <code>pause</code> a
+Deferred, it is <em>your</em> responsibility to make sure that you unpause it.
+The function adding the callbacks must unpause a paused Deferred, it should <em>never</em> be the responsibility of the code that actually fires the
+callback chain by calling <code>callback</code> or <code>errback</code> as
+this would negate its usefulness!</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/glossary.html b/doc/core/howto/glossary.html
new file mode 100644
index 0000000..c6d8ea9
--- /dev/null
+++ b/doc/core/howto/glossary.html
@@ -0,0 +1,334 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Glossary</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Glossary</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<dl>
+
+<dt><a name="adaptee" shape="rect">adaptee</a></dt>
+<dd>
+ An object that has been adapted, also called <q>original</q>. See <a href="#Adapter" shape="rect">Adapter</a>.
+</dd>
+
+<dt><a name="Adapter" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.components.Adapter.html" title="twisted.python.components.Adapter">Adapter</a></code></a></dt>
+<dd>
+ An object whose sole purpose is to implement an Interface for another object.
+ See <a href="components.html" shape="rect">Interfaces and Adapters</a>.
+</dd>
+
+<dt><a name="Application" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code></a></dt>
+<dd>
+ A <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">twisted.application.service.Application</a></code>. There are
+ HOWTOs on <a href="basics.html" shape="rect">creating and manipulating</a> them as a
+ system-administrator, as well as <a href="application.html" shape="rect">using</a> them in
+ your code.
+</dd>
+
+<dt><a name="Avatar" shape="rect">Avatar</a></dt>
+<dd>
+ (from <a href="#Cred" shape="rect">Twisted Cred</a>) business logic for specific user.
+ For example, in <a href="#PB" shape="rect">PB</a> these are perspectives, in POP3 these
+ are mailboxes, and so on.
+</dd>
+
+<dt><a name="Banana" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.banana.Banana.html" title="twisted.spread.banana.Banana">Banana</a></code></a></dt>
+<dd>
+ The low-level data marshalling layer of <a href="#Spread" shape="rect">Twisted Spread</a>.
+ See <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.banana.html" title="twisted.spread.banana">twisted.spread.banana</a></code>.
+</dd>
+
+<dt><a name="Broker" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Broker.html" title="twisted.spread.pb.Broker">Broker</a></code></a></dt>
+<dd>
+ A <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Broker.html" title="twisted.spread.pb.Broker">twisted.spread.pb.Broker</a></code>, the object request
+ broker for <a href="#Spread" shape="rect">Twisted Spread</a>.
+</dd>
+
+<dt><a name="cache" shape="rect">cache</a></dt>
+<dd>
+ A way to store data in readily accessible place for later reuse. Caching data
+ is often done because the data is expensive to produce or access. Caching data
+ risks being stale, or out of sync with the original data.
+</dd>
+
+<dt><a name="component" shape="rect">component</a></dt>
+<dd>
+ A special kind of (persistent) <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.components.Adapter.html" title="twisted.python.components.Adapter">Adapter</a></code> that works with a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.components.Componentized.html" title="twisted.python.components.Componentized">twisted.python.components.Componentized</a></code>. See also <a href="components.html" shape="rect">Interfaces and Adapters</a>.
+</dd>
+
+<dt><a name="Componentized" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.components.Componentized.html" title="twisted.python.components.Componentized">Componentized</a></code></a></dt>
+<dd>
+ A Componentized object is a collection of information, separated
+ into domain-specific or role-specific instances, that all stick
+ together and refer to each other.
+ Each object is an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.components.Adapter.html" title="twisted.python.components.Adapter">Adapter</a></code>, which, in the
+ context of Componentized, we call <q>components</q>. See also <a href="components.html" shape="rect">Interfaces and Adapters</a>.
+</dd>
+
+<dt><a name="conch" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.conch.html" title="twisted.conch">conch</a></code></a></dt>
+<dd>Twisted's SSH implementation.</dd>
+
+<dt><a name="Connector" shape="rect">Connector</a></dt>
+<dd>
+ Object used to interface between client connections and protocols, usually
+ used with a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.ClientFactory.html" title="twisted.internet.protocol.ClientFactory">twisted.internet.protocol.ClientFactory</a></code>
+ to give you control over how a client connection reconnects. See <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IConnector.html" title="twisted.internet.interfaces.IConnector">twisted.internet.interfaces.IConnector</a></code> and <a href="clients.html" shape="rect">Writing Clients</a>.
+</dd>
+
+<dt><a name="Consumer" shape="rect">Consumer</a></dt>
+<dd>
+ An object that consumes data from a <a href="#Producer" shape="rect">Producer</a>. See
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IConsumer.html" title="twisted.internet.interfaces.IConsumer">twisted.internet.interfaces.IConsumer</a></code>.
+</dd>
+
+<dt><a name="Cred" shape="rect">Cred</a></dt>
+<dd>
+ Twisted's authentication API, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.html" title="twisted.cred">twisted.cred</a></code>. See
+ <a href="cred.html" shape="rect">Introduction to Twisted Cred</a> and
+ <a href="pb-cred.html" shape="rect">Twisted Cred usage</a>.
+</dd>
+
+<dt><a name="credentials" shape="rect">credentials</a></dt>
+<dd>
+ A username/password, public key, or some other information used for
+ authentication.
+</dd>
+
+<dt><a name="credential-checker" shape="rect">credential checker</a></dt>
+<dd>
+ Where authentication actually happens. See
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.checkers.ICredentialsChecker.html" title="twisted.cred.checkers.ICredentialsChecker">ICredentialsChecker</a></code>.
+</dd>
+
+<dt><a name="CVSToys" shape="rect">CVSToys</a></dt>
+<dd>A nifty set of tools for CVS, available at
+<a href="http://twistedmatrix.com/users/acapnotic/wares/code/CVSToys/" shape="rect">http://twistedmatrix.com/users/acapnotic/wares/code/CVSToys/</a>.</dd>
+
+<dt><a name="Daemon" shape="rect">Daemon</a></dt>
+<dd>
+ A background process that does a job or handles client requests.
+ <i>Daemon</i> is a Unix term; <i>service</i> is the Windows equivalent.
+</dd>
+
+<dt><a name="Deferred" shape="rect"><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code></a></dt>
+<dd>
+ A instance of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code>, an
+ abstraction for handling chains of callbacks and error handlers
+ (<q>errbacks</q>).
+ See the <a href="defer.html" shape="rect">Deferring Execution</a> HOWTO.
+</dd>
+
+<dt><a name="Enterprise" shape="rect">Enterprise</a></dt>
+<dd>
+ Twisted's RDBMS support. It contains <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.enterprise.adbapi.html" title="twisted.enterprise.adbapi">twisted.enterprise.adbapi</a></code> for asynchronous access to any
+ standard DB-API 2.0 module. See <a href="rdbms.html" shape="rect">Introduction to
+ Twisted Enterprise</a> for more details.
+</dd>
+
+<dt><a name="errback" shape="rect">errback</a></dt>
+<dd>
+ A callback attached to a <a href="#Deferred" shape="rect">Deferred</a> with
+ <code>.addErrback</code> to handle errors.
+</dd>
+
+<dt><a name="Factory" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">Factory</a></code></a></dt>
+<dd>
+ In general, an object that constructs other objects. In Twisted, a Factory
+ usually refers to a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">twisted.internet.protocol.Factory</a></code>, which constructs
+ <a href="#Protocol" shape="rect">Protocol</a> instances for incoming or outgoing
+ connections. See <a href="servers.html" shape="rect">Writing Servers</a> and <a href="clients.html" shape="rect">Writing Clients</a>.
+</dd>
+
+<dt><a name="Failure" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code></a></dt>
+<dd>
+ Basically, an asynchronous exception that contains traceback information;
+ these are used for passing errors through asynchronous callbacks.
+</dd>
+
+<dt><a name="im" shape="rect">im</a></dt>
+<dd>
+ Abbreviation of <q>(Twisted) <a href="#InstanceMessenger" shape="rect">Instance
+ Messenger</a></q>.
+ </dd>
+
+<dt><a name="InstanceMessenger" shape="rect">Instance Messenger</a></dt>
+<dd>
+ Instance Messenger is a multi-protocol chat program that comes with
+ Twisted. It can communicate via TOC with the AOL servers, via IRC, as well as
+ via <a href="#PerspectiveBroker" shape="rect">PB</a> with
+ <a href="#Words" shape="rect">Twisted Words</a>. See <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.words.im.html" title="twisted.words.im">twisted.words.im</a></code>.
+</dd>
+
+<dt><a name="Interface" shape="rect">Interface</a></dt>
+<dd>
+ A class that defines and documents methods that a class conforming to that
+ interface needs to have. A collection of core <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.html" title="twisted.internet">twisted.internet</a></code> interfaces can
+ be found in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.html" title="twisted.internet.interfaces">twisted.internet.interfaces</a></code>. See also <a href="components.html" shape="rect">Interfaces and Adapters</a>.
+</dd>
+
+<dt><a name="Jelly" shape="rect">Jelly</a></dt>
+<dd>
+ The serialization layer for <a href="#Spread" shape="rect">Twisted Spread</a>, although it
+ can be used seperately from Twisted Spread as well. It is similar in purpose
+ to Python's standard <code>pickle</code> module, but is more
+ network-friendly, and depends on a separate marshaller (<a href="#Banana" shape="rect">Banana</a>, in most cases). See <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.html" title="twisted.spread.jelly">twisted.spread.jelly</a></code>.
+</dd>
+
+<dt><a name="Lore" shape="rect">Lore</a></dt>
+
+<dd><a href="http://twistedmatrix.com/trac/wiki/TwistedLore/" shape="rect">Lore</a> is
+Twisted's documentation system. The source format is a subset of
+XHTML, and output formats include HTML and LaTeX.</dd>
+
+<dt><a name="Manhole" shape="rect">Manhole</a></dt>
+<dd>
+ A debugging/administration interface to a Twisted application.
+</dd>
+
+<dt><a name="Microdom" shape="rect">Microdom</a></dt>
+<dd>
+ A partial DOM implementation using <a href="#SUX" shape="rect">SUX</a>. It is simple and
+ pythonic, rather than strictly standards-compliant. See <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.microdom.html" title="twisted.web.microdom">twisted.web.microdom</a></code>.
+</dd>
+
+<dt><a name="Names" shape="rect">Names</a></dt>
+<dd>Twisted's DNS server, found in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.names.html" title="twisted.names">twisted.names</a></code>.</dd>
+
+<dt><a name="Nevow" shape="rect">Nevow</a></dt>
+<dd>The successor to <a href="#Woven" shape="rect">Woven</a>; available from <a href="http://launchpad.net/nevow" shape="rect">Divmod</a>.
+</dd>
+
+<dt><a name="PB" shape="rect">PB</a></dt>
+<dd>
+ Abbreviation of <q><a href="#PerspectiveBroker" shape="rect">Perspective
+ Broker</a></q>.
+</dd>
+
+<dt><a name="PerspectiveBroker" shape="rect">Perspective Broker</a></dt>
+<dd>
+ The high-level object layer of Twisted <a href="#Spread" shape="rect">Spread</a>,
+ implementing semantics for method calling and object copying, caching, and
+ referencing. See <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.html" title="twisted.spread.pb">twisted.spread.pb</a></code>.
+</dd>
+
+<dt><a name="Portal" shape="rect">Portal</a></dt>
+<dd>
+ Glues <a href="#credential-checker" shape="rect">credential checkers</a> and
+ <a href="#realm" shape="rect">realm</a>s together.
+</dd>
+
+<dt><a name="Producer" shape="rect">Producer</a></dt>
+<dd>
+ An object that generates data a chunk at a time, usually to be processed by a
+ <a href="#Consumer" shape="rect">Consumer</a>. See
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProducer.html" title="twisted.internet.interfaces.IProducer">twisted.internet.interfaces.IProducer</a></code>.
+</dd>
+
+<dt><a name="Protocol" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">Protocol</a></code></a></dt>
+<dd>
+ In general each network connection has its own Protocol instance to manage
+ connection-specific state. There is a collection of standard
+ protocol implementations in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.protocols.html" title="twisted.protocols">twisted.protocols</a></code>. See
+ also <a href="servers.html" shape="rect">Writing Servers</a> and <a href="clients.html" shape="rect">Writing Clients</a>.
+</dd>
+
+<dt><a name="PSU" shape="rect">PSU</a></dt>
+<dd>There is no PSU.</dd>
+
+<dt><a name="Reactor" shape="rect">Reactor</a></dt>
+<dd>
+ The core event-loop of a Twisted application. See
+ <a href="reactor-basics.html" shape="rect">Reactor Basics</a>.
+</dd>
+
+<dt><a name="Reality" shape="rect">Reality</a></dt>
+<dd>See <q><a href="#TwistedReality" shape="rect">Twisted Reality</a></q></dd>
+
+<dt><a name="realm" shape="rect">realm</a></dt>
+<dd>
+ (in <a href="#Cred" shape="rect">Twisted Cred</a>) stores <a href="#Avatar" shape="rect">avatars</a>
+ and perhaps general business logic. See
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.IRealm.html" title="twisted.cred.portal.IRealm">IRealm</a></code>.
+</dd>
+
+<dt><a name="Resource" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code></a></dt>
+<dd>
+ A <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">twisted.web.resource.Resource</a></code>, which are served
+ by Twisted Web. Resources can be as simple as a static file on disk, or they
+ can have dynamically generated content.
+</dd>
+
+<dt><a name="Service" shape="rect">Service</a></dt>
+<dd>
+ A <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.Service.html" title="twisted.application.service.Service">twisted.application.service.Service</a></code>. See <a href="application.html" shape="rect">Application howto</a> for a description of how they
+ relate to <a href="#Application" shape="rect">Applications</a>.
+</dd>
+
+<dt><a name="Spread" shape="rect">Spread</a></dt>
+<dd>Twisted Spread is
+Twisted's remote-object suite. It consists of three layers:
+<a href="#PerspectiveBroker" shape="rect">Perspective Broker</a>, <a href="#Jelly" shape="rect">Jelly</a>
+and <a href="#Banana" shape="rect">Banana.</a> See <a href="pb.html" shape="rect">Writing Applications
+with Perspective Broker</a>.</dd>
+
+<dt><a name="SUX" shape="rect">SUX</a></dt>
+<dd><em>S</em>mall <em>U</em>ncomplicated <em>X</em>ML, Twisted's simple XML
+parser written in pure Python. See <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.sux.html" title="twisted.web.sux">twisted.web.sux</a></code>.</dd>
+
+<dt><a name="TAC" shape="rect">TAC</a></dt>
+<dd>A <em>T</em>wisted <em>A</em>pplication <em>C</em>onfiguration is a Python
+source file, generally with the <em>.tac</em> extension, which defines
+configuration to make an application runnable using <code>twistd</code>.</dd>
+
+<dt><a name="TAP" shape="rect">TAP</a></dt>
+<dd><em>T</em>wisted <em>A</em>pplication <em>P</em>ickle (no longer supported), or simply just a
+<em>T</em>wisted <em>AP</em>plication. A serialised application that was created
+with <code>mktap</code> (no longer supported) and runnable by <code>twistd</code>. See
+<a href="basics.html" shape="rect">Using the Utilities</a>.</dd>
+
+<dt><a name="Trial" shape="rect">Trial</a></dt>
+<dd><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.trial.html" title="twisted.trial">twisted.trial</a></code>, Twisted's unit-testing framework,
+based on the <code>unittest</code> standard library module. See also <a href="testing.html" shape="rect">Writing tests for Twisted code</a>.</dd>
+
+<dt><a name="TwistedMatrixLaboratories" shape="rect">Twisted Matrix Laboratories</a></dt>
+<dd>The team behind Twisted.
+<a href="http://twistedmatrix.com/" shape="rect">http://twistedmatrix.com/</a>.</dd>
+
+<dt><a name="TwistedReality" shape="rect">Twisted Reality</a></dt>
+<dd>
+In days of old, the Twisted Reality multiplayer text-based interactive-fiction
+system was the main focus of Twisted Matrix Labs; Twisted, the general networking
+framework, grew out of Reality's need for better network functionality. Twisted
+Reality has been superseded by the <a href="http://launchpad.net/imaginary" shape="rect">Imaginary</a> project.
+</dd>
+
+<dt><a name="usage" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.usage.html" title="twisted.python.usage">usage</a></code></a></dt>
+<dd>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.usage.html" title="twisted.python.usage">twisted.python.usage</a></code> module, a replacement for
+the standard <code>getopt</code> module for parsing command-lines which is much
+easier to work with. See <a href="options.html" shape="rect">Parsing command-lines</a>.</dd>
+
+<dt><a name="Words" shape="rect">Words</a></dt>
+<dd>Twisted Words is a multi-protocol chat server that uses the
+<a href="#PerspectiveBroker" shape="rect">Perspective Broker</a> protocol as its native
+communication style. See <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.words.html" title="twisted.words">twisted.words</a></code>.</dd>
+
+<dt><a name="Woven" shape="rect">Woven</a></dt>
+<dd><em>W</em>eb <em>O</em>bject <em>V</em>isualization <em>En</em>vironment.
+A templating system previously, but no longer, included with Twisted. Woven
+has largely been superceded by <a href="http://launchpad.net/nevow" shape="rect">
+Divmod Nevow</a>.</dd>
+
+</dl>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/howto.tidyrc b/doc/core/howto/howto.tidyrc
new file mode 100644
index 0000000..6896505
--- /dev/null
+++ b/doc/core/howto/howto.tidyrc
@@ -0,0 +1,6 @@
+output-xml: yes
+output-xhtml: yes
+tidy-mark: no
+indent: auto
+gnu-emacs: yes
+add-xml-decl: yes \ No newline at end of file
diff --git a/doc/core/howto/index.html b/doc/core/howto/index.html
new file mode 100644
index 0000000..76cb5bb
--- /dev/null
+++ b/doc/core/howto/index.html
@@ -0,0 +1,239 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <ul class="toc">
+ <li><a name="introduction" shape="rect">Introduction</a>
+
+ <ul>
+ <li><a href="vision.html" shape="rect">Executive summary</a><br clear="none"/>
+ Connecting your software - and having fun too!</li>
+ </ul>
+ </li>
+
+ <li><a name="tutorials" shape="rect">Getting Started</a>
+
+ <ul>
+ <li><a href="servers.html" shape="rect">Writing a TCP server</a><br clear="none"/>
+ Basic network servers with Twisted.</li>
+
+ <li><a href="clients.html" shape="rect">Writing a TCP client</a><br clear="none"/>
+ And basic clients.</li>
+
+ <li><a href="trial.html" shape="rect">Test-driven development with
+ Twisted</a><br clear="none"/>
+ Code without tests is broken by definition; Twisted makes it easy to test your network code.</li>
+
+ <li>
+ <a href="tutorial/index.html" shape="rect">Tutorial: Twisted From
+ Scratch</a>
+
+ <ol>
+ <li><a href="tutorial/intro.html" shape="rect">The Evolution of
+ Finger: building a simple finger service</a></li>
+
+ <li><a href="tutorial/protocol.html" shape="rect">The Evolution of
+ Finger: adding features to the finger service</a></li>
+
+ <li><a href="tutorial/style.html" shape="rect">The Evolution of
+ Finger: cleaning up the finger code</a></li>
+
+ <li><a href="tutorial/components.html" shape="rect">The Evolution of
+ Finger: moving to a component based
+ architecture</a></li>
+
+ <li><a href="tutorial/backends.html" shape="rect">The Evolution of
+ Finger: pluggable backends</a></li>
+
+ <li><a href="tutorial/web.html" shape="rect">The Evolution of
+ Finger: a clean web frontend</a></li>
+
+ <li><a href="tutorial/pb.html" shape="rect">The Evolution of Finger:
+ Twisted client support using Perspective
+ Broker</a></li>
+
+ <li><a href="tutorial/factory.html" shape="rect">The Evolution of
+ Finger: using a single factory for multiple
+ protocols</a></li>
+
+ <li><a href="tutorial/client.html" shape="rect">The Evolution of
+ Finger: a Twisted finger client</a></li>
+
+ <li><a href="tutorial/library.html" shape="rect">The Evolution of
+ Finger: making a finger library</a></li>
+
+ <li><a href="tutorial/configuration.html" shape="rect">The Evolution
+ of Finger: configuration and packaging of the finger
+ service</a></li>
+ </ol>
+ </li>
+
+ <li><a href="quotes.html" shape="rect">Setting up the TwistedQuotes
+ application</a></li>
+
+ <li><a href="design.html" shape="rect">Designing a Twisted application</a></li>
+ </ul>
+ </li>
+
+ <li><a name="events" shape="rect">Networking and Other Event Sources</a>
+
+ <ul>
+ <li><a href="internet-overview.html" shape="rect">Twisted
+ Internet</a><br clear="none"/>
+ A brief overview of the <code>twisted.internet</code> package.</li>
+
+ <li><a href="reactor-basics.html" shape="rect">Reactor basics</a><br clear="none"/>
+ The event loop at the core of your program.</li>
+
+ <li><a href="ssl.html" shape="rect">Using SSL in Twisted</a><br clear="none"/>
+ Add some security to your network transport.</li>
+
+ <li><a href="udp.html" shape="rect">UDP Networking</a><br clear="none"/>
+ Multicast too!</li>
+
+ <li><a href="process.html" shape="rect">Using processes</a><br clear="none"/>
+ Launching sub-processes, the correct way.</li>
+
+ <li><a href="defer.html" shape="rect">Using Deferreds</a><br clear="none"/>
+ Like callback functions, only a lot better.</li>
+
+ <li><a href="gendefer.html" shape="rect">Generating deferreds</a><br clear="none"/>
+ More about Deferreds.</li>
+
+ <li><a href="time.html" shape="rect">Scheduling</a><br clear="none"/>
+ Timeouts, repeated events, and more: when you want things to happen later.</li>
+
+ <li><a href="threading.html" shape="rect">Using threads</a><br clear="none"/>
+ Running code in threads, and interacting with Twisted in a thread-safe manner.</li>
+
+ <li><a href="producers.html" shape="rect">Producers and Consumers: Efficient High-Volume Streaming</a><br clear="none"/>
+ How to pause when buffers fill up.</li>
+
+ <li><a href="choosing-reactor.html" shape="rect">Choosing a reactor and
+ GUI toolkit integration</a><br clear="none"/>
+ GTK+, Windows, epoll() and more: use your GUI of choice, or a faster event loop.</li>
+ </ul>
+ </li>
+
+ <li><a name="highlevel" shape="rect">High-Level Infrastructure</a>
+ <ul>
+ <li><a href="endpoints.html" shape="rect">Getting Connected with Endpoints</a><br clear="none"/>
+ Create configurable applications that support multiple transports (e.g. TCP and SSL).</li>
+
+ <li><a href="components.html" shape="rect">Interfaces and Adapters
+ (Component Architecture)</a><br clear="none"/>
+ When inheritance isn't enough.</li>
+
+ <li><a href="cred.html" shape="rect">Cred: Pluggable
+ Authentication</a><br clear="none"/> Implementing authentication and
+ authorization that is configurable, pluggable and
+ re-usable.</li>
+
+ <li><a href="plugin.html" shape="rect">Twisted's plugin architecture</a><br clear="none"/>
+ A generic plugin system for extendable programs.</li>
+ </ul>
+ </li>
+
+ <li><a name="deploying" shape="rect">Deploying Twisted Applications</a>
+
+ <ul>
+ <li><a href="basics.html" shape="rect">Helper programs and scripts
+ (twistd, ..)</a><br clear="none"/>
+ <code>twistd</code> lets you daemonize and run your
+ application.</li>
+
+ <li><a href="application.html" shape="rect">Using the Twisted Application
+ Framework</a><br clear="none"/>
+ Writing code that <code>twistd</code> can run.</li>
+
+ <li><a href="tap.html" shape="rect">Writing Twisted Application Plugins
+ for twistd</a><br clear="none"/>
+ More powerful <code>twistd</code> deployment method.</li>
+ </ul>
+ </li>
+
+ <li><a name="utilities" shape="rect">Utilities</a>
+
+ <ul>
+ <li><a href="logging.html" shape="rect">Logging</a><br clear="none"/>
+ Keep a record of what your application is up to.</li>
+
+ <li><a href="constants.html" shape="rect">Symbolic constants</a><br clear="none"/>
+ enum-like constants.</li>
+
+ <li><a href="rdbms.html" shape="rect">Twisted RDBMS support with
+ adbapi</a><br clear="none"/>
+ Using SQL with your relational database via DB-API adapters.</li>
+
+ <li><a href="options.html" shape="rect">Parsing command-line arguments</a><br clear="none"/>
+ The command-line argument parsing used by <code>twistd</code>.</li>
+
+ <li><a href="dirdbm.html" shape="rect">Using Dirdbm: Directory-based Storage</a><br clear="none"/>
+ A simplistic way to store data on your filesystem.</li>
+
+ <li><a href="testing.html" shape="rect">Tips for writing tests for Twisted
+ code using Trial</a><br clear="none"/>
+ More information on writing tests.</li>
+
+ <li><a href="sendmsg.html" shape="rect">Extremely Low-Level Socket Operations</a><br clear="none"/>
+ Using wrappers for sendmsg(2) and recvmsg(2).</li>
+ </ul>
+ </li>
+
+ <li><a name="amp" shape="rect">Asynchronous Messaging Protocol (AMP)</a>
+
+ <ul>
+ <li><a href="amp.html" shape="rect">Asynchronous Messaging Protocol Overview</a><br clear="none"/>
+ A two-way asynchronous message passing protocol, for when HTTP isn't good enough.</li>
+ </ul>
+ </li>
+
+ <li><a name="pb" shape="rect">Perspective Broker</a>
+ <ul>
+ <li><a href="pb.html" shape="rect">Twisted Spread</a><br clear="none"/>
+ A remote method invocation (RMI) protocol: call methods on remote objects.</li>
+
+ <li><a href="pb-intro.html" shape="rect">Introduction to Perspective
+ Broker</a></li>
+
+ <li><a href="pb-usage.html" shape="rect">Using Perspective
+ Broker</a></li>
+
+ <li><a href="pb-clients.html" shape="rect">Managing Clients of
+ Perspectives</a></li>
+
+ <li><a href="pb-copyable.html" shape="rect">Passing Complex
+ Types</a></li>
+
+ <li><a href="pb-cred.html" shape="rect">Authentication with Perspective
+ Broker</a></li>
+
+ <li><a href="pb-limits.html" shape="rect">PB Limits</a></li>
+ </ul>
+ </li>
+
+ <li><a name="appendix" shape="rect">Appendix</a>
+
+ <ul>
+ <li><a href="glossary.html" shape="rect">Glossary</a></li>
+
+ <li class="ignoretoc"><a href="debug-with-emacs.html" shape="rect">Tips
+ for debugging with emacs</a></li>
+ </ul>
+ </li>
+ </ul>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/internet-overview.html b/doc/core/howto/internet-overview.html
new file mode 100644
index 0000000..2da29dd
--- /dev/null
+++ b/doc/core/howto/internet-overview.html
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Overview of Twisted Internet</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Overview of Twisted Internet</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<p>Twisted Internet is a collection of compatible event-loops for Python.
+It contains the code to dispatch events to interested observers and a portable
+API so that observers need not care about which event loop is running. Thus,
+it is possible to use the same code for different loops, from Twisted's basic,
+yet portable, <code>select</code>-based loop to the loops of various GUI
+toolkits like GTK+ or Tk.</p>
+
+<p>Twisted Internet contains the various interfaces to the reactor
+API, whose usage is documented in the low-level chapter. Those APIs
+are <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorCore.html" title="twisted.internet.interfaces.IReactorCore">IReactorCore</a></code>,
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTCP.html" title="twisted.internet.interfaces.IReactorTCP">IReactorTCP</a></code>,
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorSSL.html" title="twisted.internet.interfaces.IReactorSSL">IReactorSSL</a></code>,
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUNIX.html" title="twisted.internet.interfaces.IReactorUNIX">IReactorUNIX</a></code>,
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUDP.html" title="twisted.internet.interfaces.IReactorUDP">IReactorUDP</a></code>,
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTime.html" title="twisted.internet.interfaces.IReactorTime">IReactorTime</a></code>,
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorProcess.html" title="twisted.internet.interfaces.IReactorProcess">IReactorProcess</a></code>,
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorMulticast.html" title="twisted.internet.interfaces.IReactorMulticast">IReactorMulticast</a></code>
+and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorThreads.html" title="twisted.internet.interfaces.IReactorThreads">IReactorThreads</a></code>.
+The reactor APIs allow non-persistent calls to be made.</p>
+
+<p>Twisted Internet also covers the interfaces for the various transports,
+in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.ITransport.html" title="twisted.internet.interfaces.ITransport">ITransport</a></code>
+and friends. These interfaces allow Twisted network code to be written without
+regard to the underlying implementation of the transport.</p>
+
+<p>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProtocolFactory.html" title="twisted.internet.interfaces.IProtocolFactory">IProtocolFactory</a></code>
+dictates how factories, which are usually a large part of third party code, are
+written.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/listings/TwistedQuotes/__init__.py b/doc/core/howto/listings/TwistedQuotes/__init__.py
new file mode 100644
index 0000000..ed6bd97
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/__init__.py
@@ -0,0 +1,3 @@
+"""
+Twisted Quotes
+"""
diff --git a/doc/core/howto/listings/TwistedQuotes/pbquote.py b/doc/core/howto/listings/TwistedQuotes/pbquote.py
new file mode 100644
index 0000000..d0330e6
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/pbquote.py
@@ -0,0 +1,10 @@
+from twisted.spread import pb
+
+class QuoteReader(pb.Root):
+
+ def __init__(self, quoter):
+ self.quoter = quoter
+
+ def remote_nextQuote(self):
+ return self.quoter.getQuote()
+
diff --git a/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py b/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py
new file mode 100644
index 0000000..c297539
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py
@@ -0,0 +1,32 @@
+
+from sys import stdout
+from twisted.python import log
+log.discardLogs()
+from twisted.internet import reactor
+from twisted.spread import pb
+
+def connected(root):
+ root.callRemote('nextQuote').addCallbacks(success, failure)
+
+def success(quote):
+ stdout.write(quote + "\n")
+ reactor.stop()
+
+def failure(error):
+ stdout.write("Failed to obtain quote.\n")
+ reactor.stop()
+
+factory = pb.PBClientFactory()
+reactor.connectTCP(
+ "localhost", # host name
+ pb.portno, # port number
+ factory, # factory
+ )
+
+
+
+factory.getRootObject().addCallbacks(connected, # when we get the root
+ failure) # when we can't
+
+reactor.run() # start the main loop
+
diff --git a/doc/core/howto/listings/TwistedQuotes/quoteproto.py b/doc/core/howto/listings/TwistedQuotes/quoteproto.py
new file mode 100644
index 0000000..b8d3469
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/quoteproto.py
@@ -0,0 +1,36 @@
+from zope.interface import Interface
+
+from twisted.internet.protocol import Factory, Protocol
+
+
+
+class IQuoter(Interface):
+ """
+ An object that returns quotes.
+ """
+ def getQuote():
+ """
+ Return a quote.
+ """
+
+
+
+class QOTD(Protocol):
+ def connectionMade(self):
+ self.transport.write(self.factory.quoter.getQuote()+'\r\n')
+ self.transport.loseConnection()
+
+
+
+class QOTDFactory(Factory):
+ """
+ A factory for the Quote of the Day protocol.
+
+ @type quoter: L{IQuoter} provider
+ @ivar quoter: An object which provides L{IQuoter} which will be used by
+ the L{QOTD} protocol to get quotes to emit.
+ """
+ protocol = QOTD
+
+ def __init__(self, quoter):
+ self.quoter = quoter
diff --git a/doc/core/howto/listings/TwistedQuotes/quoters.py b/doc/core/howto/listings/TwistedQuotes/quoters.py
new file mode 100644
index 0000000..f6d5689
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/quoters.py
@@ -0,0 +1,39 @@
+from random import choice
+
+from zope.interface import implements
+
+from TwistedQuotes import quoteproto
+
+
+
+class StaticQuoter:
+ """
+ Return a static quote.
+ """
+
+ implements(quoteproto.IQuoter)
+
+ def __init__(self, quote):
+ self.quote = quote
+
+
+ def getQuote(self):
+ return self.quote
+
+
+
+class FortuneQuoter:
+ """
+ Load quotes from a fortune-format file.
+ """
+ implements(quoteproto.IQuoter)
+
+ def __init__(self, filenames):
+ self.filenames = filenames
+
+
+ def getQuote(self):
+ quoteFile = file(choice(self.filenames))
+ quotes = quoteFile.read().split('\n%\n')
+ quoteFile.close()
+ return choice(quotes)
diff --git a/doc/core/howto/listings/TwistedQuotes/quotes.txt b/doc/core/howto/listings/TwistedQuotes/quotes.txt
new file mode 100644
index 0000000..62a5ed9
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/quotes.txt
@@ -0,0 +1,15 @@
+
+<radix> the sysadmin of the future is going to know twisted-shelling like the back of his hand
+%
+<Acapnotic> Ooh, I just figured out what my first twisted.reality creation will be.
+<dash> Acapnotic: oh?
+<Acapnotic> "Being Glyph Lefkowitz"
+%
+<johs> Oh, please. Threads ownz j00.
+%
+<jafo> I used to hang out with this chick that ran a BBS.
+<jafo> She had a great baud.
+%
+<chrchr> dsmith: Twisted is neat, but unfortunately, it's not object-oriented.
+%
+<datazone> twisted is madness
diff --git a/doc/core/howto/listings/TwistedQuotes/quotetap.py b/doc/core/howto/listings/TwistedQuotes/quotetap.py
new file mode 100644
index 0000000..06d15ec
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/quotetap.py
@@ -0,0 +1,29 @@
+from twisted.application import internet # services that run TCP/SSL/etc.
+from TwistedQuotes import quoteproto # Protocol and Factory
+from TwistedQuotes import quoters # "give me a quote" code
+
+from twisted.python import usage # twisted command-line processing
+
+
+class Options(usage.Options):
+ optParameters = [["port", "p", 8007,
+ "Port number to listen on for QOTD protocol."],
+ ["static", "s", "An apple a day keeps the doctor away.",
+ "A static quote to display."],
+ ["file", "f", None,
+ "A fortune-format text file to read quotes from."]]
+
+
+def makeService(config):
+ """Return a service that will be attached to the application."""
+ if config["file"]: # If I was given a "file" option...
+ # Read quotes from a file, selecting a random one each time,
+ quoter = quoters.FortuneQuoter([config['file']])
+ else: # otherwise,
+ # read a single quote from the command line (or use the default).
+ quoter = quoters.StaticQuoter(config['static'])
+ port = int(config["port"]) # TCP port to listen on
+ factory = quoteproto.QOTDFactory(quoter) # here we create a QOTDFactory
+ # Finally, set up our factory, with its custom quoter, to create QOTD
+ # protocol instances when events arrive on the specified port.
+ return internet.TCPServer(port, factory)
diff --git a/doc/core/howto/listings/TwistedQuotes/quotetap2.py b/doc/core/howto/listings/TwistedQuotes/quotetap2.py
new file mode 100644
index 0000000..4bc0f06
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/quotetap2.py
@@ -0,0 +1,36 @@
+from TwistedQuotes import quoteproto # Protocol and Factory
+from TwistedQuotes import quoters # "give me a quote" code
+from TwistedQuotes import pbquote # perspective broker binding
+
+from twisted.application import service, internet
+from twisted.python import usage # twisted command-line processing
+from twisted.spread import pb # Perspective Broker
+
+class Options(usage.Options):
+ optParameters = [["port", "p", 8007,
+ "Port number to listen on for QOTD protocol."],
+ ["static", "s", "An apple a day keeps the doctor away.",
+ "A static quote to display."],
+ ["file", "f", None,
+ "A fortune-format text file to read quotes from."],
+ ["pb", "b", None,
+ "Port to listen with PB server"]]
+
+def makeService(config):
+ svc = service.MultiService()
+ if config["file"]: # If I was given a "file" option...
+ # Read quotes from a file, selecting a random one each time,
+ quoter = quoters.FortuneQuoter([config['file']])
+ else: # otherwise,
+ # read a single quote from the command line (or use the default).
+ quoter = quoters.StaticQuoter(config['static'])
+ port = int(config["port"]) # TCP port to listen on
+ factory = quoteproto.QOTDFactory(quoter) # here we create a QOTDFactory
+ # Finally, set up our factory, with its custom quoter, to create QOTD
+ # protocol instances when events arrive on the specified port.
+ pbport = config['pb'] # TCP PB port to listen on
+ if pbport:
+ pbfact = pb.PBServerFactory(pbquote.QuoteReader(quoter))
+ svc.addService(internet.TCPServer(int(pbport), pbfact))
+ svc.addService(internet.TCPServer(port, factory))
+ return svc
diff --git a/doc/core/howto/listings/TwistedQuotes/webquote.rpy b/doc/core/howto/listings/TwistedQuotes/webquote.rpy
new file mode 100644
index 0000000..99e0e9c
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/webquote.rpy
@@ -0,0 +1,12 @@
+# -*- Python -*-
+
+from TwistedQuotes import webquoteresource
+
+#__file__ is defined to be the name of this file; this is to
+#get the sibling file "quotes.txt" which should be in the same directory
+import os
+quotefile = os.path.join(os.path.split(__file__)[0], "quotes.txt")
+
+#ResourceScript requires us to define 'resource'.
+#This resource is used to render the page.
+resource = webquoteresource.QuoteResource([quotefile])
diff --git a/doc/core/howto/listings/amp/basic_client.py b/doc/core/howto/listings/amp/basic_client.py
new file mode 100644
index 0000000..6d99b68
--- /dev/null
+++ b/doc/core/howto/listings/amp/basic_client.py
@@ -0,0 +1,30 @@
+
+if __name__ == '__main__':
+ import basic_client
+ raise SystemExit(basic_client.main())
+
+from sys import stdout
+
+from twisted.python.log import startLogging, err
+from twisted.protocols.amp import AMP
+from twisted.internet import reactor
+from twisted.internet.protocol import Factory
+from twisted.internet.endpoints import TCP4ClientEndpoint
+
+def connect():
+ endpoint = TCP4ClientEndpoint(reactor, "127.0.0.1", 8750)
+ factory = Factory()
+ factory.protocol = AMP
+ return endpoint.connect(factory)
+
+
+def main():
+ startLogging(stdout)
+
+ d = connect()
+ d.addErrback(err, "Connection failed")
+ def done(ignored):
+ reactor.stop()
+ d.addCallback(done)
+
+ reactor.run()
diff --git a/doc/core/howto/listings/amp/basic_server.tac b/doc/core/howto/listings/amp/basic_server.tac
new file mode 100644
index 0000000..c28f663
--- /dev/null
+++ b/doc/core/howto/listings/amp/basic_server.tac
@@ -0,0 +1,14 @@
+from twisted.protocols.amp import AMP
+from twisted.internet import reactor
+from twisted.internet.protocol import Factory
+from twisted.internet.endpoints import TCP4ServerEndpoint
+from twisted.application.service import Application
+from twisted.application.internet import StreamServerEndpointService
+
+application = Application("basic AMP server")
+
+endpoint = TCP4ServerEndpoint(reactor, 8750)
+factory = Factory()
+factory.protocol = AMP
+service = StreamServerEndpointService(endpoint, factory)
+service.setServiceParent(application)
diff --git a/doc/core/howto/listings/amp/command_client.py b/doc/core/howto/listings/amp/command_client.py
new file mode 100644
index 0000000..8aa4cbd
--- /dev/null
+++ b/doc/core/howto/listings/amp/command_client.py
@@ -0,0 +1,48 @@
+
+if __name__ == '__main__':
+ import command_client
+ raise SystemExit(command_client.main())
+
+from sys import stdout
+
+from twisted.python.log import startLogging, err
+from twisted.protocols.amp import Integer, String, Unicode, Command
+from twisted.internet import reactor
+
+from basic_client import connect
+
+class UsernameUnavailable(Exception):
+ pass
+
+
+class RegisterUser(Command):
+ arguments = [('username', Unicode()),
+ ('publickey', String())]
+
+ response = [('uid', Integer())]
+
+ errors = {UsernameUnavailable: 'username-unavailable'}
+
+
+def main():
+ startLogging(stdout)
+
+ d = connect()
+ def connected(protocol):
+ return protocol.callRemote(
+ RegisterUser,
+ username=u'alice',
+ publickey='ssh-rsa AAAAB3NzaC1yc2 alice@actinium')
+ d.addCallback(connected)
+
+ def registered(result):
+ print 'Registration result:', result
+ d.addCallback(registered)
+
+ d.addErrback(err, "Failed to register")
+
+ def finished(ignored):
+ reactor.stop()
+ d.addCallback(finished)
+
+ reactor.run()
diff --git a/doc/core/howto/listings/application/service.tac b/doc/core/howto/listings/application/service.tac
new file mode 100644
index 0000000..b0167fa
--- /dev/null
+++ b/doc/core/howto/listings/application/service.tac
@@ -0,0 +1,34 @@
+# You can run this .tac file directly with:
+# twistd -ny service.tac
+
+"""
+This is an example .tac file which starts a webserver on port 8080 and
+serves files from the current working directory.
+
+The important part of this, the part that makes it a .tac file, is
+the final root-level section, which sets up the object called 'application'
+which twistd will look for
+"""
+
+import os
+from twisted.application import service, internet
+from twisted.web import static, server
+
+def getWebService():
+ """
+ Return a service suitable for creating an application object.
+
+ This service is a simple web server that serves files on port 8080 from
+ underneath the current working directory.
+ """
+ # create a resource to serve static files
+ fileServer = server.Site(static.File(os.getcwd()))
+ return internet.TCPServer(8080, fileServer)
+
+# this is the core part of any tac file, the creation of the root-level
+# application object
+application = service.Application("Demo application")
+
+# attach the service to its parent application
+service = getWebService()
+service.setServiceParent(application)
diff --git a/doc/core/howto/listings/deferred/synch-validation.py b/doc/core/howto/listings/deferred/synch-validation.py
new file mode 100644
index 0000000..2912f2b
--- /dev/null
+++ b/doc/core/howto/listings/deferred/synch-validation.py
@@ -0,0 +1,5 @@
+def synchronousIsValidUser(user):
+ '''
+ Return true if user is a valid user, false otherwise
+ '''
+ return user in ["Alice", "Angus", "Agnes"]
diff --git a/doc/core/howto/listings/pb/cache_classes.py b/doc/core/howto/listings/pb/cache_classes.py
new file mode 100755
index 0000000..0e3493e
--- /dev/null
+++ b/doc/core/howto/listings/pb/cache_classes.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+
+class MasterDuckPond(pb.Cacheable):
+ def __init__(self, ducks):
+ self.observers = []
+ self.ducks = ducks
+ def count(self):
+ print "I have [%d] ducks" % len(self.ducks)
+ def addDuck(self, duck):
+ self.ducks.append(duck)
+ for o in self.observers: o.callRemote('addDuck', duck)
+ def removeDuck(self, duck):
+ self.ducks.remove(duck)
+ for o in self.observers: o.callRemote('removeDuck', duck)
+ def getStateToCacheAndObserveFor(self, perspective, observer):
+ self.observers.append(observer)
+ # you should ignore pb.Cacheable-specific state, like self.observers
+ return self.ducks # in this case, just a list of ducks
+ def stoppedObserving(self, perspective, observer):
+ self.observers.remove(observer)
+
+class SlaveDuckPond(pb.RemoteCache):
+ # This is a cache of a remote MasterDuckPond
+ def count(self):
+ return len(self.cacheducks)
+ def getDucks(self):
+ return self.cacheducks
+ def setCopyableState(self, state):
+ print " cache - sitting, er, setting ducks"
+ self.cacheducks = state
+ def observe_addDuck(self, newDuck):
+ print " cache - addDuck"
+ self.cacheducks.append(newDuck)
+ def observe_removeDuck(self, deadDuck):
+ print " cache - removeDuck"
+ self.cacheducks.remove(deadDuck)
+
+pb.setUnjellyableForClass(MasterDuckPond, SlaveDuckPond)
diff --git a/doc/core/howto/listings/pb/cache_receiver.py b/doc/core/howto/listings/pb/cache_receiver.py
new file mode 100755
index 0000000..b6d930b
--- /dev/null
+++ b/doc/core/howto/listings/pb/cache_receiver.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application import service, internet
+from twisted.internet import reactor
+from twisted.spread import pb
+import cache_classes
+
+class Receiver(pb.Root):
+ def remote_takePond(self, pond):
+ self.pond = pond
+ print "got pond:", pond # a DuckPondCache
+ self.remote_checkDucks()
+ def remote_checkDucks(self):
+ print "[%d] ducks: " % self.pond.count(), self.pond.getDucks()
+ def remote_ignorePond(self):
+ # stop watching the pond
+ print "dropping pond"
+ # gc causes __del__ causes 'decache' msg causes stoppedObserving
+ self.pond = None
+ def remote_shutdown(self):
+ reactor.stop()
+
+application = service.Application("copy_receiver")
+internet.TCPServer(8800, pb.PBServerFactory(Receiver())).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/doc/core/howto/listings/pb/cache_sender.py b/doc/core/howto/listings/pb/cache_sender.py
new file mode 100755
index 0000000..391143a
--- /dev/null
+++ b/doc/core/howto/listings/pb/cache_sender.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+from cache_classes import MasterDuckPond
+
+class Sender:
+ def __init__(self, pond):
+ self.pond = pond
+
+ def phase1(self, remote):
+ self.remote = remote
+ d = remote.callRemote("takePond", self.pond)
+ d.addCallback(self.phase2).addErrback(log.err)
+ def phase2(self, response):
+ self.pond.addDuck("ugly duckling")
+ self.pond.count()
+ reactor.callLater(1, self.phase3)
+ def phase3(self):
+ d = self.remote.callRemote("checkDucks")
+ d.addCallback(self.phase4).addErrback(log.err)
+ def phase4(self, dummy):
+ self.pond.removeDuck("one duck")
+ self.pond.count()
+ self.remote.callRemote("checkDucks")
+ d = self.remote.callRemote("ignorePond")
+ d.addCallback(self.phase5)
+ def phase5(self, dummy):
+ d = self.remote.callRemote("shutdown")
+ d.addCallback(self.phase6)
+ def phase6(self, dummy):
+ reactor.stop()
+
+def main():
+ master = MasterDuckPond(["one duck", "two duck"])
+ master.count()
+
+ sender = Sender(master)
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ deferred = factory.getRootObject()
+ deferred.addCallback(sender.phase1)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/pb/chatclient.py b/doc/core/howto/listings/pb/chatclient.py
new file mode 100755
index 0000000..eb2f677
--- /dev/null
+++ b/doc/core/howto/listings/pb/chatclient.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+class Client(pb.Referenceable):
+
+ def remote_print(self, message):
+ print message
+
+ def connect(self):
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("alice", "1234"),
+ client=self)
+ def1.addCallback(self.connected)
+ reactor.run()
+
+ def connected(self, perspective):
+ print "connected, joining group #NeedAFourth"
+ # this perspective is a reference to our User object. Save a reference
+ # to it here, otherwise it will get garbage collected after this call,
+ # and the server will think we logged out.
+ self.perspective = perspective
+ d = perspective.callRemote("joinGroup", "#NeedAFourth")
+ d.addCallback(self.gotGroup)
+
+ def gotGroup(self, group):
+ print "joined group, now sending a message to all members"
+ # 'group' is a reference to the Group object (through a ViewPoint)
+ d = group.callRemote("send", "You can call me Al.")
+ d.addCallback(self.shutdown)
+
+ def shutdown(self, result):
+ reactor.stop()
+
+
+Client().connect()
+
diff --git a/doc/core/howto/listings/pb/chatserver.py b/doc/core/howto/listings/pb/chatserver.py
new file mode 100755
index 0000000..ff70d2a
--- /dev/null
+++ b/doc/core/howto/listings/pb/chatserver.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import implements
+
+from twisted.cred import portal, checkers
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class ChatServer:
+ def __init__(self):
+ self.groups = {} # indexed by name
+
+ def joinGroup(self, groupname, user, allowMattress):
+ if not self.groups.has_key(groupname):
+ self.groups[groupname] = Group(groupname, allowMattress)
+ self.groups[groupname].addUser(user)
+ return self.groups[groupname]
+
+class ChatRealm:
+ implements(portal.IRealm)
+ def requestAvatar(self, avatarID, mind, *interfaces):
+ assert pb.IPerspective in interfaces
+ avatar = User(avatarID)
+ avatar.server = self.server
+ avatar.attached(mind)
+ return pb.IPerspective, avatar, lambda a=avatar:a.detached(mind)
+
+class User(pb.Avatar):
+ def __init__(self, name):
+ self.name = name
+ def attached(self, mind):
+ self.remote = mind
+ def detached(self, mind):
+ self.remote = None
+ def perspective_joinGroup(self, groupname, allowMattress=True):
+ return self.server.joinGroup(groupname, self, allowMattress)
+ def send(self, message):
+ self.remote.callRemote("print", message)
+
+class Group(pb.Viewable):
+ def __init__(self, groupname, allowMattress):
+ self.name = groupname
+ self.allowMattress = allowMattress
+ self.users = []
+ def addUser(self, user):
+ self.users.append(user)
+ def view_send(self, from_user, message):
+ if not self.allowMattress and "mattress" in message:
+ raise ValueError, "Don't say that word"
+ for user in self.users:
+ user.send("<%s> says: %s" % (from_user.name, message))
+
+realm = ChatRealm()
+realm.server = ChatServer()
+checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+checker.addUser("alice", "1234")
+checker.addUser("bob", "secret")
+checker.addUser("carol", "fido")
+p = portal.Portal(realm, [checker])
+
+reactor.listenTCP(8800, pb.PBServerFactory(p))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/copy2_classes.py b/doc/core/howto/listings/pb/copy2_classes.py
new file mode 100755
index 0000000..8b11e14
--- /dev/null
+++ b/doc/core/howto/listings/pb/copy2_classes.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+
+class FrogPond:
+ def __init__(self, numFrogs, numToads):
+ self.numFrogs = numFrogs
+ self.numToads = numToads
+ def count(self):
+ return self.numFrogs + self.numToads
+
+class SenderPond(FrogPond, pb.Copyable):
+ def getStateToCopy(self):
+ d = self.__dict__.copy()
+ d['frogsAndToads'] = d['numFrogs'] + d['numToads']
+ del d['numFrogs']
+ del d['numToads']
+ return d
+
+class ReceiverPond(pb.RemoteCopy):
+ def setCopyableState(self, state):
+ self.__dict__ = state
+ def count(self):
+ return self.frogsAndToads
+
+pb.setUnjellyableForClass(SenderPond, ReceiverPond)
diff --git a/doc/core/howto/listings/pb/copy2_receiver.py b/doc/core/howto/listings/pb/copy2_receiver.py
new file mode 100755
index 0000000..76f06c3
--- /dev/null
+++ b/doc/core/howto/listings/pb/copy2_receiver.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application import service, internet
+from twisted.internet import reactor
+from twisted.spread import pb
+import copy2_classes # needed to get ReceiverPond registered with Jelly
+
+class Receiver(pb.Root):
+ def remote_takePond(self, pond):
+ print " got pond:", pond
+ print " count %d" % pond.count()
+ return "safe and sound" # positive acknowledgement
+ def remote_shutdown(self):
+ reactor.stop()
+
+application = service.Application("copy_receiver")
+internet.TCPServer(8800, pb.PBServerFactory(Receiver())).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/doc/core/howto/listings/pb/copy2_sender.py b/doc/core/howto/listings/pb/copy2_sender.py
new file mode 100755
index 0000000..5b008b7
--- /dev/null
+++ b/doc/core/howto/listings/pb/copy2_sender.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+from copy2_classes import SenderPond
+
+class Sender:
+ def __init__(self, pond):
+ self.pond = pond
+
+ def got_obj(self, obj):
+ d = obj.callRemote("takePond", self.pond)
+ d.addCallback(self.ok).addErrback(self.notOk)
+
+ def ok(self, response):
+ print "pond arrived", response
+ reactor.stop()
+ def notOk(self, failure):
+ print "error during takePond:"
+ if failure.type == jelly.InsecureJelly:
+ print " InsecureJelly"
+ else:
+ print failure
+ reactor.stop()
+ return None
+
+def main():
+ pond = SenderPond(3, 4)
+ print "count %d" % pond.count()
+
+ sender = Sender(pond)
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ deferred = factory.getRootObject()
+ deferred.addCallback(sender.got_obj)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/doc/core/howto/listings/pb/copy_receiver.tac b/doc/core/howto/listings/pb/copy_receiver.tac
new file mode 100755
index 0000000..dc9905e
--- /dev/null
+++ b/doc/core/howto/listings/pb/copy_receiver.tac
@@ -0,0 +1,41 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+PB copy receiver example.
+
+This is a Twisted Application Configuration (tac) file. Run with e.g.
+ twistd -ny copy_receiver.tac
+
+See the twistd(1) man page or
+http://twistedmatrix.com/documents/current/howto/application for details.
+"""
+
+import sys
+if __name__ == '__main__':
+ print __doc__
+ sys.exit(1)
+
+from twisted.application import service, internet
+from twisted.internet import reactor
+from twisted.spread import pb
+from copy_sender import LilyPond, CopyPond
+
+from twisted.python import log
+#log.startLogging(sys.stdout)
+
+class ReceiverPond(pb.RemoteCopy, LilyPond):
+ pass
+pb.setUnjellyableForClass(CopyPond, ReceiverPond)
+
+class Receiver(pb.Root):
+ def remote_takePond(self, pond):
+ print " got pond:", pond
+ pond.countFrogs()
+ return "safe and sound" # positive acknowledgement
+ def remote_shutdown(self):
+ reactor.stop()
+
+application = service.Application("copy_receiver")
+internet.TCPServer(8800, pb.PBServerFactory(Receiver())).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/doc/core/howto/listings/pb/copy_sender.py b/doc/core/howto/listings/pb/copy_sender.py
new file mode 100755
index 0000000..fca0594
--- /dev/null
+++ b/doc/core/howto/listings/pb/copy_sender.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+
+class LilyPond:
+ def setStuff(self, color, numFrogs):
+ self.color = color
+ self.numFrogs = numFrogs
+ def countFrogs(self):
+ print "%d frogs" % self.numFrogs
+
+class CopyPond(LilyPond, pb.Copyable):
+ pass
+
+class Sender:
+ def __init__(self, pond):
+ self.pond = pond
+
+ def got_obj(self, remote):
+ self.remote = remote
+ d = remote.callRemote("takePond", self.pond)
+ d.addCallback(self.ok).addErrback(self.notOk)
+
+ def ok(self, response):
+ print "pond arrived", response
+ reactor.stop()
+ def notOk(self, failure):
+ print "error during takePond:"
+ if failure.type == jelly.InsecureJelly:
+ print " InsecureJelly"
+ else:
+ print failure
+ reactor.stop()
+ return None
+
+def main():
+ from copy_sender import CopyPond # so it's not __main__.CopyPond
+ pond = CopyPond()
+ pond.setStuff("green", 7)
+ pond.countFrogs()
+ # class name:
+ print ".".join([pond.__class__.__module__, pond.__class__.__name__])
+
+ sender = Sender(pond)
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ deferred = factory.getRootObject()
+ deferred.addCallback(sender.got_obj)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/pb/exc_client.py b/doc/core/howto/listings/pb/exc_client.py
new file mode 100755
index 0000000..cedd107
--- /dev/null
+++ b/doc/core/howto/listings/pb/exc_client.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ d = factory.getRootObject()
+ d.addCallbacks(got_obj)
+ reactor.run()
+
+def got_obj(obj):
+ # change "broken" into "broken2" to demonstrate an unhandled exception
+ d2 = obj.callRemote("broken")
+ d2.addCallback(working)
+ d2.addErrback(broken)
+
+def working():
+ print "erm, it wasn't *supposed* to work.."
+
+def broken(reason):
+ print "got remote Exception"
+ # reason should be a Failure (or subclass) holding the MyError exception
+ print " .__class__ =", reason.__class__
+ print " .getErrorMessage() =", reason.getErrorMessage()
+ print " .type =", reason.type
+ reactor.stop()
+
+main()
diff --git a/doc/core/howto/listings/pb/exc_server.py b/doc/core/howto/listings/pb/exc_server.py
new file mode 100755
index 0000000..9cb3dab
--- /dev/null
+++ b/doc/core/howto/listings/pb/exc_server.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class MyError(pb.Error):
+ """This is an Expected Exception. Something bad happened."""
+ pass
+
+class MyError2(Exception):
+ """This is an Unexpected Exception. Something really bad happened."""
+ pass
+
+class One(pb.Root):
+ def remote_broken(self):
+ msg = "fall down go boom"
+ print "raising a MyError exception with data '%s'" % msg
+ raise MyError(msg)
+ def remote_broken2(self):
+ msg = "hadda owie"
+ print "raising a MyError2 exception with data '%s'" % msg
+ raise MyError2(msg)
+
+def main():
+ reactor.listenTCP(8800, pb.PBServerFactory(One()))
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/pb/pb1client.py b/doc/core/howto/listings/pb/pb1client.py
new file mode 100755
index 0000000..8525f16
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb1client.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.getRootObject()
+ def1.addCallbacks(got_obj1, err_obj1)
+ reactor.run()
+
+def err_obj1(reason):
+ print "error getting first object", reason
+ reactor.stop()
+
+def got_obj1(obj1):
+ print "got first object:", obj1
+ print "asking it to getTwo"
+ def2 = obj1.callRemote("getTwo")
+ def2.addCallbacks(got_obj2)
+
+def got_obj2(obj2):
+ print "got second object:", obj2
+ print "telling it to do three(12)"
+ obj2.callRemote("three", 12)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb1server.py b/doc/core/howto/listings/pb/pb1server.py
new file mode 100755
index 0000000..d927489
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb1server.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+
+class Two(pb.Referenceable):
+ def remote_three(self, arg):
+ print "Two.three was given", arg
+
+class One(pb.Root):
+ def remote_getTwo(self):
+ two = Two()
+ print "returning a Two called", two
+ return two
+
+from twisted.internet import reactor
+reactor.listenTCP(8800, pb.PBServerFactory(One()))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/pb2client.py b/doc/core/howto/listings/pb/pb2client.py
new file mode 100755
index 0000000..0164447
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb2client.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ foo = Foo()
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ factory.getRootObject().addCallback(foo.step1)
+ reactor.run()
+
+# keeping globals around is starting to get ugly, so we use a simple class
+# instead. Instead of hooking one function to the next, we hook one method
+# to the next.
+
+class Foo:
+ def __init__(self):
+ self.oneRef = None
+
+ def step1(self, obj):
+ print "got one object:", obj
+ self.oneRef = obj
+ print "asking it to getTwo"
+ self.oneRef.callRemote("getTwo").addCallback(self.step2)
+
+ def step2(self, two):
+ print "got two object:", two
+ print "giving it back to one"
+ print "one is", self.oneRef
+ self.oneRef.callRemote("checkTwo", two)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb2server.py b/doc/core/howto/listings/pb/pb2server.py
new file mode 100755
index 0000000..c32344e
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb2server.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Two(pb.Referenceable):
+ def remote_print(self, arg):
+ print "two.print was given", arg
+
+class One(pb.Root):
+ def __init__(self, two):
+ #pb.Root.__init__(self) # pb.Root doesn't implement __init__
+ self.two = two
+ def remote_getTwo(self):
+ print "One.getTwo(), returning my two called", self.two
+ return self.two
+ def remote_checkTwo(self, newtwo):
+ print "One.checkTwo(): comparing my two", self.two
+ print "One.checkTwo(): against your two", newtwo
+ if self.two == newtwo:
+ print "One.checkTwo(): our twos are the same"
+
+
+two = Two()
+root_obj = One(two)
+reactor.listenTCP(8800, pb.PBServerFactory(root_obj))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/pb3client.py b/doc/core/howto/listings/pb/pb3client.py
new file mode 100755
index 0000000..28a9749
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb3client.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Two(pb.Referenceable):
+ def remote_print(self, arg):
+ print "Two.print() called with", arg
+
+def main():
+ two = Two()
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.getRootObject()
+ def1.addCallback(got_obj, two) # hands our 'two' to the callback
+ reactor.run()
+
+def got_obj(obj, two):
+ print "got One:", obj
+ print "giving it our two"
+ obj.callRemote("takeTwo", two)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb3server.py b/doc/core/howto/listings/pb/pb3server.py
new file mode 100755
index 0000000..f71b4a1
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb3server.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class One(pb.Root):
+ def remote_takeTwo(self, two):
+ print "received a Two called", two
+ print "telling it to print(12)"
+ two.callRemote("print", 12)
+
+reactor.listenTCP(8800, pb.PBServerFactory(One()))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/pb4client.py b/doc/core/howto/listings/pb/pb4client.py
new file mode 100755
index 0000000..10f2694
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb4client.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ rootobj_def = pb.getObjectAt("localhost", 8800, 30)
+ rootobj_def.addCallbacks(got_rootobj)
+ obj2_def = getSomeObjectAt("localhost", 8800, 30, "two")
+ obj2_def.addCallbacks(got_obj2)
+ obj3_def = getSomeObjectAt("localhost", 8800, 30, "three")
+ obj3_def.addCallbacks(got_obj3)
+ reactor.run()
+
+def got_rootobj(rootobj):
+ print "got root object:", rootobj
+ print "telling root object to do foo(A)"
+ rootobj.callRemote("foo", "A")
+
+def got_obj2(obj2):
+ print "got second object:", obj2
+ print "telling second object to do foo(B)"
+ obj2.callRemote("foo", "B")
+
+def got_obj3(obj3):
+ print "got third object:", obj3
+ print "telling third object to do foo(C)"
+ obj3.callRemote("foo", "C")
+
+class my_ObjectRetrieval(pb._ObjectRetrieval):
+ def __init__(self, broker, d, objname):
+ pb._ObjectRetrieval.__init__(self, broker, d)
+ self.objname = objname
+ def connectionMade(self):
+ assert not self.term, "How did this get called?"
+ x = self.broker.remoteForName(self.objname)
+ del self.broker
+ self.term = 1
+ self.deferred.callback(x)
+
+def getSomeObjectAt(host, port, timeout=None, objname="root"):
+ from twisted.internet import defer
+ from twisted.spread.pb import Broker, BrokerClientFactory
+ d = defer.Deferred()
+ b = Broker(1)
+ bf = BrokerClientFactory(b)
+ my_ObjectRetrieval(b, d, objname)
+ if host == "unix":
+ # every time you use this, God kills a kitten
+ reactor.connectUNIX(port, bf, timeout)
+ else:
+ reactor.connectTCP(host, port, bf, timeout)
+ return d
+
+main()
diff --git a/doc/core/howto/listings/pb/pb5client.py b/doc/core/howto/listings/pb/pb5client.py
new file mode 100755
index 0000000..3026780
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb5client.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("user1", "pass1"))
+ def1.addCallback(connected)
+ reactor.run()
+
+def connected(perspective):
+ print "got perspective ref:", perspective
+ print "asking it to foo(12)"
+ perspective.callRemote("foo", 12)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb5server.py b/doc/core/howto/listings/pb/pb5server.py
new file mode 100755
index 0000000..dc95f6e
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb5server.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.cred import checkers, portal
+from twisted.internet import reactor
+
+class MyPerspective(pb.Avatar):
+ def __init__(self, name):
+ self.name = name
+ def perspective_foo(self, arg):
+ print "I am", self.name, "perspective_foo(",arg,") called on", self
+
+class MyRealm:
+ implements(portal.IRealm)
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective not in interfaces:
+ raise NotImplementedError
+ return pb.IPerspective, MyPerspective(avatarId), lambda:None
+
+p = portal.Portal(MyRealm())
+p.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(user1="pass1"))
+reactor.listenTCP(8800, pb.PBServerFactory(p))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/pb6client1.py b/doc/core/howto/listings/pb/pb6client1.py
new file mode 100755
index 0000000..38ae65a
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb6client1.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("user1", "pass1"))
+ def1.addCallback(connected)
+ reactor.run()
+
+def connected(perspective):
+ print "got perspective1 ref:", perspective
+ print "asking it to foo(13)"
+ perspective.callRemote("foo", 13)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb6client2.py b/doc/core/howto/listings/pb/pb6client2.py
new file mode 100755
index 0000000..ecceb2a
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb6client2.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("user2", "pass2"))
+ def1.addCallback(connected)
+ reactor.run()
+
+def connected(perspective):
+ print "got perspective2 ref:", perspective
+ print "asking it to foo(14)"
+ perspective.callRemote("foo", 14)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb6server.py b/doc/core/howto/listings/pb/pb6server.py
new file mode 100755
index 0000000..9c98d5e
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb6server.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.cred import checkers, portal
+from twisted.internet import reactor
+
+class MyPerspective(pb.Avatar):
+ def __init__(self, name):
+ self.name = name
+ def perspective_foo(self, arg):
+ print "I am", self.name, "perspective_foo(",arg,") called on", self
+
+class MyRealm:
+ implements(portal.IRealm)
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective not in interfaces:
+ raise NotImplementedError
+ return pb.IPerspective, MyPerspective(avatarId), lambda:None
+
+p = portal.Portal(MyRealm())
+c = checkers.InMemoryUsernamePasswordDatabaseDontUse(user1="pass1",
+ user2="pass2")
+p.registerChecker(c)
+reactor.listenTCP(8800, pb.PBServerFactory(p))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/pb7client.py b/doc/core/howto/listings/pb/pb7client.py
new file mode 100755
index 0000000..e212fb2
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb7client.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def one(port, user, pw, service, perspective, number):
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", port, factory)
+ def1 = factory.getPerspective(
+ user, pw, service, perspective)
+ def1.addCallback(connected, number)
+
+def connected(perspective, number):
+ print "got perspective ref:", perspective
+ print "asking it to foo(%d)" % number
+ perspective.callRemote("foo", number)
+
+def main():
+ one(8800, "user1", "pass1", "service1", "perspective1.1", 10)
+ one(8800, "user1", "pass1", "service2", "perspective2.1", 11)
+ one(8800, "user2", "pass2", "service1", "perspective1.2", 12)
+ one(8800, "user2", "pass2", "service2", "perspective2.2", 13)
+ one(8801, "user3", "pass3", "service3", "perspective3.3", 14)
+ reactor.run()
+
+main()
diff --git a/doc/core/howto/listings/pb/pbAnonClient.py b/doc/core/howto/listings/pb/pbAnonClient.py
new file mode 100755
index 0000000..fbd9a9f
--- /dev/null
+++ b/doc/core/howto/listings/pb/pbAnonClient.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Client which will talk to the server run by pbAnonServer.py, logging in
+either anonymously or with username/password credentials.
+"""
+
+from sys import stdout
+
+from twisted.python.log import err, startLogging
+from twisted.cred.credentials import Anonymous, UsernamePassword
+from twisted.internet import reactor
+from twisted.internet.defer import gatherResults
+from twisted.spread.pb import PBClientFactory
+
+
+def error(why, msg):
+ """
+ Catch-all errback which simply logs the failure. This isn't expected to
+ be invoked in the normal case for this example.
+ """
+ err(why, msg)
+
+
+def connected(perspective):
+ """
+ Login callback which invokes the remote "foo" method on the perspective
+ which the server returned.
+ """
+ print "got perspective1 ref:", perspective
+ print "asking it to foo(13)"
+ return perspective.callRemote("foo", 13)
+
+
+def finished(ignored):
+ """
+ Callback invoked when both logins and method calls have finished to shut
+ down the reactor so the example exits.
+ """
+ reactor.stop()
+
+
+def main():
+ """
+ Connect to a PB server running on port 8800 on localhost and log in to
+ it, both anonymously and using a username/password it will recognize.
+ """
+ startLogging(stdout)
+ factory = PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+
+ anonymousLogin = factory.login(Anonymous())
+ anonymousLogin.addCallback(connected)
+ anonymousLogin.addErrback(error, "Anonymous login failed")
+
+ usernameLogin = factory.login(UsernamePassword("user1", "pass1"))
+ usernameLogin.addCallback(connected)
+ usernameLogin.addErrback(error, "Username/password login failed")
+
+ bothDeferreds = gatherResults([anonymousLogin, usernameLogin])
+ bothDeferreds.addCallback(finished)
+
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/pb/pbAnonServer.py b/doc/core/howto/listings/pb/pbAnonServer.py
new file mode 100755
index 0000000..f5eadac
--- /dev/null
+++ b/doc/core/howto/listings/pb/pbAnonServer.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implement the realm for and run on port 8800 a PB service which allows both
+anonymous and username/password based access.
+
+Successful username/password-based login requests given an instance of
+MyPerspective with a name which matches the username with which they
+authenticated. Success anonymous login requests are given an instance of
+MyPerspective with the name "Anonymous".
+"""
+
+from sys import stdout
+
+from zope.interface import implements
+
+from twisted.python.log import startLogging
+from twisted.cred.checkers import ANONYMOUS, AllowAnonymousAccess
+from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+from twisted.cred.portal import IRealm, Portal
+from twisted.internet import reactor
+from twisted.spread.pb import Avatar, IPerspective, PBServerFactory
+
+
+class MyPerspective(Avatar):
+ """
+ Trivial avatar exposing a single remote method for demonstrative
+ purposes. All successful login attempts in this example will result in
+ an avatar which is an instance of this class.
+
+ @type name: C{str}
+ @ivar name: The username which was used during login or C{"Anonymous"}
+ if the login was anonymous (a real service might want to avoid the
+ collision this introduces between anonoymous users and authenticated
+ users named "Anonymous").
+ """
+ def __init__(self, name):
+ self.name = name
+
+
+ def perspective_foo(self, arg):
+ """
+ Print a simple message which gives the argument this method was
+ called with and this avatar's name.
+ """
+ print "I am %s. perspective_foo(%s) called on %s." % (
+ self.name, arg, self)
+
+
+
+class MyRealm(object):
+ """
+ Trivial realm which supports anonymous and named users by creating
+ avatars which are instances of MyPerspective for either.
+ """
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if IPerspective not in interfaces:
+ raise NotImplementedError("MyRealm only handles IPerspective")
+ if avatarId is ANONYMOUS:
+ avatarId = "Anonymous"
+ return IPerspective, MyPerspective(avatarId), lambda: None
+
+
+
+def main():
+ """
+ Create a PB server using MyRealm and run it on port 8800.
+ """
+ startLogging(stdout)
+
+ p = Portal(MyRealm())
+
+ # Here the username/password checker is registered.
+ c1 = InMemoryUsernamePasswordDatabaseDontUse(user1="pass1", user2="pass2")
+ p.registerChecker(c1)
+
+ # Here the anonymous checker is registered.
+ c2 = AllowAnonymousAccess()
+ p.registerChecker(c2)
+
+ reactor.listenTCP(8800, PBServerFactory(p))
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/pb/trap_client.py b/doc/core/howto/listings/pb/trap_client.py
new file mode 100755
index 0000000..7fb2a9a
--- /dev/null
+++ b/doc/core/howto/listings/pb/trap_client.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+
+class MyException(pb.Error): pass
+class MyOtherException(pb.Error): pass
+
+class ScaryObject:
+ # not safe for serialization
+ pass
+
+def worksLike(obj):
+ # the callback/errback sequence in class One works just like an
+ # asynchronous version of the following:
+ try:
+ response = obj.callMethod(name, arg)
+ except pb.DeadReferenceError:
+ print " stale reference: the client disconnected or crashed"
+ except jelly.InsecureJelly:
+ print " InsecureJelly: you tried to send something unsafe to them"
+ except (MyException, MyOtherException):
+ print " remote raised a MyException" # or MyOtherException
+ except:
+ print " something else happened"
+ else:
+ print " method successful, response:", response
+
+class One:
+ def worked(self, response):
+ print " method successful, response:", response
+ def check_InsecureJelly(self, failure):
+ failure.trap(jelly.InsecureJelly)
+ print " InsecureJelly: you tried to send something unsafe to them"
+ return None
+ def check_MyException(self, failure):
+ which = failure.trap(MyException, MyOtherException)
+ if which == MyException:
+ print " remote raised a MyException"
+ else:
+ print " remote raised a MyOtherException"
+ return None
+ def catch_everythingElse(self, failure):
+ print " something else happened"
+ log.err(failure)
+ return None
+
+ def doCall(self, explanation, arg):
+ print explanation
+ try:
+ deferred = self.remote.callRemote("fooMethod", arg)
+ deferred.addCallback(self.worked)
+ deferred.addErrback(self.check_InsecureJelly)
+ deferred.addErrback(self.check_MyException)
+ deferred.addErrback(self.catch_everythingElse)
+ except pb.DeadReferenceError:
+ print " stale reference: the client disconnected or crashed"
+
+ def callOne(self):
+ self.doCall("callOne: call with safe object", "safe string")
+ def callTwo(self):
+ self.doCall("callTwo: call with dangerous object", ScaryObject())
+ def callThree(self):
+ self.doCall("callThree: call that raises remote exception", "panic!")
+ def callShutdown(self):
+ print "telling them to shut down"
+ self.remote.callRemote("shutdown")
+ def callFour(self):
+ self.doCall("callFour: call on stale reference", "dummy")
+
+ def got_obj(self, obj):
+ self.remote = obj
+ reactor.callLater(1, self.callOne)
+ reactor.callLater(2, self.callTwo)
+ reactor.callLater(3, self.callThree)
+ reactor.callLater(4, self.callShutdown)
+ reactor.callLater(5, self.callFour)
+ reactor.callLater(6, reactor.stop)
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("localhost", 8800, factory)
+deferred = factory.getRootObject()
+deferred.addCallback(One().got_obj)
+reactor.run()
diff --git a/doc/core/howto/listings/pb/trap_server.py b/doc/core/howto/listings/pb/trap_server.py
new file mode 100755
index 0000000..03bd92c
--- /dev/null
+++ b/doc/core/howto/listings/pb/trap_server.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import reactor
+from twisted.spread import pb
+
+class MyException(pb.Error):
+ pass
+
+class One(pb.Root):
+ def remote_fooMethod(self, arg):
+ if arg == "panic!":
+ raise MyException
+ return "response"
+ def remote_shutdown(self):
+ reactor.stop()
+
+reactor.listenTCP(8800, pb.PBServerFactory(One()))
+reactor.run()
diff --git a/doc/core/howto/listings/process/process.py b/doc/core/howto/listings/process/process.py
new file mode 100755
index 0000000..95579e5
--- /dev/null
+++ b/doc/core/howto/listings/process/process.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import protocol
+from twisted.internet import reactor
+import re
+
+class MyPP(protocol.ProcessProtocol):
+ def __init__(self, verses):
+ self.verses = verses
+ self.data = ""
+ def connectionMade(self):
+ print "connectionMade!"
+ for i in range(self.verses):
+ self.transport.write("Aleph-null bottles of beer on the wall,\n" +
+ "Aleph-null bottles of beer,\n" +
+ "Take one down and pass it around,\n" +
+ "Aleph-null bottles of beer on the wall.\n")
+ self.transport.closeStdin() # tell them we're done
+ def outReceived(self, data):
+ print "outReceived! with %d bytes!" % len(data)
+ self.data = self.data + data
+ def errReceived(self, data):
+ print "errReceived! with %d bytes!" % len(data)
+ def inConnectionLost(self):
+ print "inConnectionLost! stdin is closed! (we probably did it)"
+ def outConnectionLost(self):
+ print "outConnectionLost! The child closed their stdout!"
+ # now is the time to examine what they wrote
+ #print "I saw them write:", self.data
+ (dummy, lines, words, chars, file) = re.split(r'\s+', self.data)
+ print "I saw %s lines" % lines
+ def errConnectionLost(self):
+ print "errConnectionLost! The child closed their stderr."
+ def processExited(self, reason):
+ print "processExited, status %d" % (reason.value.exitCode,)
+ def processEnded(self, reason):
+ print "processEnded, status %d" % (reason.value.exitCode,)
+ print "quitting"
+ reactor.stop()
+
+pp = MyPP(10)
+reactor.spawnProcess(pp, "wc", ["wc"], {})
+reactor.run()
diff --git a/doc/core/howto/listings/process/quotes.py b/doc/core/howto/listings/process/quotes.py
new file mode 100644
index 0000000..c0efeaf
--- /dev/null
+++ b/doc/core/howto/listings/process/quotes.py
@@ -0,0 +1,25 @@
+from twisted.internet import protocol, utils, reactor
+from twisted.python import failure
+from cStringIO import StringIO
+
+class FortuneQuoter(protocol.Protocol):
+
+ fortune = '/usr/games/fortune'
+
+ def connectionMade(self):
+ output = utils.getProcessOutput(self.fortune)
+ output.addCallbacks(self.writeResponse, self.noResponse)
+
+ def writeResponse(self, resp):
+ self.transport.write(resp)
+ self.transport.loseConnection()
+
+ def noResponse(self, err):
+ self.transport.loseConnection()
+
+
+if __name__ == '__main__':
+ f = protocol.Factory()
+ f.protocol = FortuneQuoter
+ reactor.listenTCP(10999, f)
+ reactor.run()
diff --git a/doc/core/howto/listings/process/trueandfalse.py b/doc/core/howto/listings/process/trueandfalse.py
new file mode 100644
index 0000000..4962c93
--- /dev/null
+++ b/doc/core/howto/listings/process/trueandfalse.py
@@ -0,0 +1,14 @@
+from twisted.internet import utils, reactor
+
+def printTrueValue(val):
+ print "/bin/true exits with rc=%d" % val
+ output = utils.getProcessValue('/bin/false')
+ output.addCallback(printFalseValue)
+
+def printFalseValue(val):
+ print "/bin/false exits with rc=%d" % val
+ reactor.stop()
+
+output = utils.getProcessValue('/bin/true')
+output.addCallback(printTrueValue)
+reactor.run()
diff --git a/doc/core/howto/listings/sendmsg/copy_descriptor.py b/doc/core/howto/listings/sendmsg/copy_descriptor.py
new file mode 100644
index 0000000..1c9a774
--- /dev/null
+++ b/doc/core/howto/listings/sendmsg/copy_descriptor.py
@@ -0,0 +1,35 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Demonstration of copying a file descriptor over an AF_UNIX connection using
+sendmsg.
+"""
+
+from os import pipe, read, write
+from socket import SOL_SOCKET, socketpair
+from struct import unpack, pack
+
+from twisted.python.sendmsg import SCM_RIGHTS, send1msg, recv1msg
+
+def main():
+ foo, bar = socketpair()
+ reader, writer = pipe()
+
+ # Send a copy of the descriptor. Notice that there must be at least one
+ # byte of normal data passed in.
+ sent = send1msg(
+ foo.fileno(), "\x00", 0,
+ [(SOL_SOCKET, SCM_RIGHTS, pack("i", reader))])
+
+ # Receive the copy, including that one byte of normal data.
+ data, flags, ancillary = recv1msg(bar.fileno(), 1024)
+ duplicate = unpack("i", ancillary[0][2])[0]
+
+ # Demonstrate that the copy works just like the original
+ write(writer, "Hello, world")
+ print "Read from original (%d): %r" % (reader, read(reader, 6))
+ print "Read from duplicate (%d): %r" % (duplicate, read(duplicate, 6))
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/sendmsg/send_replacement.py b/doc/core/howto/listings/sendmsg/send_replacement.py
new file mode 100644
index 0000000..9460c8e
--- /dev/null
+++ b/doc/core/howto/listings/sendmsg/send_replacement.py
@@ -0,0 +1,21 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Demonstration of sending bytes over a TCP connection using sendmsg.
+"""
+
+from socket import socketpair
+
+from twisted.python.sendmsg import send1msg, recv1msg
+
+def main():
+ foo, bar = socketpair()
+ sent = send1msg(foo.fileno(), "Hello, world")
+ print "Sent", sent, "bytes"
+ (received, flags, ancillary) = recv1msg(bar.fileno(), 1024)
+ print "Received", repr(received)
+ print "Extra stuff, boring in this case", flags, ancillary
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/servers/chat.py b/doc/core/howto/listings/servers/chat.py
new file mode 100644
index 0000000..01795bf
--- /dev/null
+++ b/doc/core/howto/listings/servers/chat.py
@@ -0,0 +1,51 @@
+from twisted.internet.protocol import Factory
+from twisted.protocols.basic import LineReceiver
+from twisted.internet import reactor
+
+class Chat(LineReceiver):
+
+ def __init__(self, users):
+ self.users = users
+ self.name = None
+ self.state = "GETNAME"
+
+ def connectionMade(self):
+ self.sendLine("What's your name?")
+
+ def connectionLost(self, reason):
+ if self.users.has_key(self.name):
+ del self.users[self.name]
+
+ def lineReceived(self, line):
+ if self.state == "GETNAME":
+ self.handle_GETNAME(line)
+ else:
+ self.handle_CHAT(line)
+
+ def handle_GETNAME(self, name):
+ if self.users.has_key(name):
+ self.sendLine("Name taken, please choose another.")
+ return
+ self.sendLine("Welcome, %s!" % (name,))
+ self.name = name
+ self.users[name] = self
+ self.state = "CHAT"
+
+ def handle_CHAT(self, message):
+ message = "<%s> %s" % (self.name, message)
+ for name, protocol in self.users.iteritems():
+ if protocol != self:
+ protocol.sendLine(message)
+
+
+class ChatFactory(Factory):
+
+ def __init__(self):
+ self.users = {} # maps user names to Chat instances
+
+ def buildProtocol(self, addr):
+ return Chat(self.users)
+
+
+reactor.listenTCP(8123, ChatFactory())
+reactor.run()
diff --git a/doc/core/howto/listings/trial/calculus/__init__.py b/doc/core/howto/listings/trial/calculus/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/__init__.py
diff --git a/doc/core/howto/listings/trial/calculus/base_1.py b/doc/core/howto/listings/trial/calculus/base_1.py
new file mode 100644
index 0000000..e827263
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/base_1.py
@@ -0,0 +1,16 @@
+# -*- test-case-name: calculus.test.test_base_1 -*-
+
+
+
+class Calculation(object):
+ def add(self, a, b):
+ pass
+
+ def subtract(self, a, b):
+ pass
+
+ def multiply(self, a, b):
+ pass
+
+ def divide(self, a, b):
+ pass
diff --git a/doc/core/howto/listings/trial/calculus/base_2.py b/doc/core/howto/listings/trial/calculus/base_2.py
new file mode 100644
index 0000000..9644912
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/base_2.py
@@ -0,0 +1,14 @@
+# -*- test-case-name: calculus.test.test_base_2 -*-
+
+class Calculation(object):
+ def add(self, a, b):
+ return a + b
+
+ def subtract(self, a, b):
+ return a - b
+
+ def multiply(self, a, b):
+ return a * b
+
+ def divide(self, a, b):
+ return a / b
diff --git a/doc/core/howto/listings/trial/calculus/base_3.py b/doc/core/howto/listings/trial/calculus/base_3.py
new file mode 100644
index 0000000..bd33c31
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/base_3.py
@@ -0,0 +1,24 @@
+# -*- test-case-name: calculus.test.test_base_3 -*-
+
+class Calculation(object):
+ def _make_ints(self, *args):
+ try:
+ return map(int, args)
+ except ValueError:
+ raise TypeError("Couldn't coerce arguments to integers: %s" % args)
+
+ def add(self, a, b):
+ a, b = self._make_ints(a, b)
+ return a + b
+
+ def subtract(self, a, b):
+ a, b = self._make_ints(a, b)
+ return a - b
+
+ def multiply(self, a, b):
+ a, b = self._make_ints(a, b)
+ return a * b
+
+ def divide(self, a, b):
+ a, b = self._make_ints(a, b)
+ return a / b
diff --git a/doc/core/howto/listings/trial/calculus/client_1.py b/doc/core/howto/listings/trial/calculus/client_1.py
new file mode 100644
index 0000000..a42434d
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/client_1.py
@@ -0,0 +1,39 @@
+# -*- test-case-name: calculus.test.test_client_1 -*-
+
+from twisted.protocols import basic
+from twisted.internet import defer
+
+
+
+class RemoteCalculationClient(basic.LineReceiver):
+ def __init__(self):
+ self.results = []
+
+
+ def lineReceived(self, line):
+ d = self.results.pop(0)
+ d.callback(int(line))
+
+
+ def _sendOperation(self, op, a, b):
+ d = defer.Deferred()
+ self.results.append(d)
+ line = "%s %d %d" % (op, a, b)
+ self.sendLine(line)
+ return d
+
+
+ def add(self, a, b):
+ return self._sendOperation("add", a, b)
+
+
+ def subtract(self, a, b):
+ return self._sendOperation("subtract", a, b)
+
+
+ def multiply(self, a, b):
+ return self._sendOperation("multiply", a, b)
+
+
+ def divide(self, a, b):
+ return self._sendOperation("divide", a, b)
diff --git a/doc/core/howto/listings/trial/calculus/client_2.py b/doc/core/howto/listings/trial/calculus/client_2.py
new file mode 100644
index 0000000..dfb464b
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/client_2.py
@@ -0,0 +1,54 @@
+# -*- test-case-name: calculus.test.test_client_2 -*-
+
+from twisted.protocols import basic
+from twisted.internet import defer, reactor
+
+
+
+class ClientTimeoutError(Exception):
+ pass
+
+
+
+class RemoteCalculationClient(basic.LineReceiver):
+
+ callLater = reactor.callLater
+ timeOut = 60
+
+ def __init__(self):
+ self.results = []
+
+
+ def lineReceived(self, line):
+ d, callID = self.results.pop(0)
+ callID.cancel()
+ d.callback(int(line))
+
+
+ def _cancel(self, d):
+ d.errback(ClientTimeoutError())
+
+
+ def _sendOperation(self, op, a, b):
+ d = defer.Deferred()
+ callID = self.callLater(self.timeOut, self._cancel, d)
+ self.results.append((d, callID))
+ line = "%s %d %d" % (op, a, b)
+ self.sendLine(line)
+ return d
+
+
+ def add(self, a, b):
+ return self._sendOperation("add", a, b)
+
+
+ def subtract(self, a, b):
+ return self._sendOperation("subtract", a, b)
+
+
+ def multiply(self, a, b):
+ return self._sendOperation("multiply", a, b)
+
+
+ def divide(self, a, b):
+ return self._sendOperation("divide", a, b)
diff --git a/doc/core/howto/listings/trial/calculus/client_3.py b/doc/core/howto/listings/trial/calculus/client_3.py
new file mode 100644
index 0000000..31b0c35
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/client_3.py
@@ -0,0 +1,53 @@
+# -*- test-case-name: calculus.test.test_client -*-
+
+from twisted.protocols import basic, policies
+from twisted.internet import defer
+
+
+
+class ClientTimeoutError(Exception):
+ pass
+
+
+
+class RemoteCalculationClient(object, basic.LineReceiver, policies.TimeoutMixin):
+
+ def __init__(self):
+ self.results = []
+ self._timeOut = 60
+
+ def lineReceived(self, line):
+ self.setTimeout(None)
+ d = self.results.pop(0)
+ d.callback(int(line))
+
+
+ def timeoutConnection(self):
+ for d in self.results:
+ d.errback(ClientTimeoutError())
+ self.transport.loseConnection()
+
+
+ def _sendOperation(self, op, a, b):
+ d = defer.Deferred()
+ self.results.append(d)
+ line = "%s %d %d" % (op, a, b)
+ self.sendLine(line)
+ self.setTimeout(self._timeOut)
+ return d
+
+
+ def add(self, a, b):
+ return self._sendOperation("add", a, b)
+
+
+ def subtract(self, a, b):
+ return self._sendOperation("subtract", a, b)
+
+
+ def multiply(self, a, b):
+ return self._sendOperation("multiply", a, b)
+
+
+ def divide(self, a, b):
+ return self._sendOperation("divide", a, b)
diff --git a/doc/core/howto/listings/trial/calculus/remote_1.py b/doc/core/howto/listings/trial/calculus/remote_1.py
new file mode 100644
index 0000000..4fcdd74
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/remote_1.py
@@ -0,0 +1,47 @@
+# -*- test-case-name: calculus.test.test_remote_1 -*-
+
+from twisted.protocols import basic
+from twisted.internet import protocol
+from calculus.base_3 import Calculation
+
+
+
+class CalculationProxy(object):
+ def __init__(self):
+ self.calc = Calculation()
+ for m in ['add', 'subtract', 'multiply', 'divide']:
+ setattr(self, 'remote_%s' % m, getattr(self.calc, m))
+
+
+
+class RemoteCalculationProtocol(basic.LineReceiver):
+ def __init__(self):
+ self.proxy = CalculationProxy()
+
+
+ def lineReceived(self, line):
+ op, a, b = line.split()
+ a = int(a)
+ b = int(b)
+ op = getattr(self.proxy, 'remote_%s' % (op,))
+ result = op(a, b)
+ self.sendLine(str(result))
+
+
+
+class RemoteCalculationFactory(protocol.Factory):
+ protocol = RemoteCalculationProtocol
+
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.python import log
+ import sys
+ log.startLogging(sys.stdout)
+ reactor.listenTCP(0, RemoteCalculationFactory())
+ reactor.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/doc/core/howto/listings/trial/calculus/remote_2.py b/doc/core/howto/listings/trial/calculus/remote_2.py
new file mode 100644
index 0000000..6826be1
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/remote_2.py
@@ -0,0 +1,51 @@
+# -*- test-case-name: calculus.test.test_remote_1 -*-
+
+from twisted.protocols import basic
+from twisted.internet import protocol
+from twisted.python import log
+from calculus.base_3 import Calculation
+
+
+
+class CalculationProxy(object):
+ def __init__(self):
+ self.calc = Calculation()
+ for m in ['add', 'subtract', 'multiply', 'divide']:
+ setattr(self, 'remote_%s' % m, getattr(self.calc, m))
+
+
+
+class RemoteCalculationProtocol(basic.LineReceiver):
+ def __init__(self):
+ self.proxy = CalculationProxy()
+
+
+ def lineReceived(self, line):
+ op, a, b = line.split()
+ op = getattr(self.proxy, 'remote_%s' % (op,))
+ try:
+ result = op(a, b)
+ except TypeError:
+ log.err()
+ self.sendLine("error")
+ else:
+ self.sendLine(str(result))
+
+
+
+class RemoteCalculationFactory(protocol.Factory):
+ protocol = RemoteCalculationProtocol
+
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.python import log
+ import sys
+ log.startLogging(sys.stdout)
+ reactor.listenTCP(0, RemoteCalculationFactory())
+ reactor.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/doc/core/howto/listings/trial/calculus/test/__init__.py b/doc/core/howto/listings/trial/calculus/test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/__init__.py
diff --git a/doc/core/howto/listings/trial/calculus/test/test_base_1.py b/doc/core/howto/listings/trial/calculus/test/test_base_1.py
new file mode 100644
index 0000000..09f0b14
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_base_1.py
@@ -0,0 +1,23 @@
+from calculus.base_1 import Calculation
+from twisted.trial import unittest
+
+class CalculationTestCase(unittest.TestCase):
+ def test_add(self):
+ calc = Calculation()
+ result = calc.add(3, 8)
+ self.assertEqual(result, 11)
+
+ def test_subtract(self):
+ calc = Calculation()
+ result = calc.subtract(7, 3)
+ self.assertEqual(result, 4)
+
+ def test_multiply(self):
+ calc = Calculation()
+ result = calc.multiply(12, 5)
+ self.assertEqual(result, 60)
+
+ def test_divide(self):
+ calc = Calculation()
+ result = calc.divide(12, 5)
+ self.assertEqual(result, 2)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_base_2.py b/doc/core/howto/listings/trial/calculus/test/test_base_2.py
new file mode 100644
index 0000000..b9bdcb1
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_base_2.py
@@ -0,0 +1,29 @@
+from calculus.base_2 import Calculation
+from twisted.trial import unittest
+
+
+
+class CalculationTestCase(unittest.TestCase):
+
+ def test_add(self):
+ calc = Calculation()
+ result = calc.add(3, 8)
+ self.assertEqual(result, 11)
+
+
+ def test_subtract(self):
+ calc = Calculation()
+ result = calc.subtract(7, 3)
+ self.assertEqual(result, 4)
+
+
+ def test_multiply(self):
+ calc = Calculation()
+ result = calc.multiply(12, 5)
+ self.assertEqual(result, 60)
+
+
+ def test_divide(self):
+ calc = Calculation()
+ result = calc.divide(12, 5)
+ self.assertEqual(result, 2)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_base_2b.py b/doc/core/howto/listings/trial/calculus/test/test_base_2b.py
new file mode 100644
index 0000000..c05135d
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_base_2b.py
@@ -0,0 +1,29 @@
+from calculus.base_2 import Calculation
+from twisted.trial import unittest
+
+
+
+class CalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.calc = Calculation()
+
+
+ def _test(self, operation, a, b, expected):
+ result = operation(a, b)
+ self.assertEqual(result, expected)
+
+
+ def test_add(self):
+ self._test(self.calc.add, 3, 8, 11)
+
+
+ def test_subtract(self):
+ self._test(self.calc.subtract, 7, 3, 4)
+
+
+ def test_multiply(self):
+ self._test(self.calc.multiply, 6, 9, 54)
+
+
+ def test_divide(self):
+ self._test(self.calc.divide, 12, 5, 2)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_base_3.py b/doc/core/howto/listings/trial/calculus/test/test_base_3.py
new file mode 100644
index 0000000..e22541e
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_base_3.py
@@ -0,0 +1,52 @@
+from calculus.base_3 import Calculation
+from twisted.trial import unittest
+
+
+
+class CalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.calc = Calculation()
+
+
+ def _test(self, operation, a, b, expected):
+ result = operation(a, b)
+ self.assertEqual(result, expected)
+
+
+ def _test_error(self, operation):
+ self.assertRaises(TypeError, operation, "foo", 2)
+ self.assertRaises(TypeError, operation, "bar", "egg")
+ self.assertRaises(TypeError, operation, [3], [8, 2])
+ self.assertRaises(TypeError, operation, {"e": 3}, {"r": "t"})
+
+
+ def test_add(self):
+ self._test(self.calc.add, 3, 8, 11)
+
+
+ def test_subtract(self):
+ self._test(self.calc.subtract, 7, 3, 4)
+
+
+ def test_multiply(self):
+ self._test(self.calc.multiply, 6, 9, 54)
+
+
+ def test_divide(self):
+ self._test(self.calc.divide, 12, 5, 2)
+
+
+ def test_errorAdd(self):
+ self._test_error(self.calc.add)
+
+
+ def test_errorSubtract(self):
+ self._test_error(self.calc.subtract)
+
+
+ def test_errorMultiply(self):
+ self._test_error(self.calc.multiply)
+
+
+ def test_errorDivide(self):
+ self._test_error(self.calc.divide)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_client_1.py b/doc/core/howto/listings/trial/calculus/test/test_client_1.py
new file mode 100644
index 0000000..28c3408
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_client_1.py
@@ -0,0 +1,37 @@
+from calculus.client_1 import RemoteCalculationClient
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+
+class ClientCalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.tr = proto_helpers.StringTransport()
+ self.proto = RemoteCalculationClient()
+ self.proto.makeConnection(self.tr)
+
+
+ def _test(self, operation, a, b, expected):
+ d = getattr(self.proto, operation)(a, b)
+ self.assertEqual(self.tr.value(), '%s %d %d\r\n' % (operation, a, b))
+ self.tr.clear()
+ d.addCallback(self.assertEqual, expected)
+ self.proto.dataReceived("%d\r\n" % (expected,))
+ return d
+
+
+ def test_add(self):
+ return self._test('add', 7, 6, 13)
+
+
+ def test_subtract(self):
+ return self._test('subtract', 82, 78, 4)
+
+
+ def test_multiply(self):
+ return self._test('multiply', 2, 8, 16)
+
+
+ def test_divide(self):
+ return self._test('divide', 14, 3, 4)
+
diff --git a/doc/core/howto/listings/trial/calculus/test/test_client_2.py b/doc/core/howto/listings/trial/calculus/test/test_client_2.py
new file mode 100644
index 0000000..45c0a28
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_client_2.py
@@ -0,0 +1,48 @@
+from calculus.client_2 import RemoteCalculationClient, ClientTimeoutError
+
+from twisted.internet import task
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+
+class ClientCalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.tr = proto_helpers.StringTransportWithDisconnection()
+ self.clock = task.Clock()
+ self.proto = RemoteCalculationClient()
+ self.tr.protocol = self.proto
+ self.proto.callLater = self.clock.callLater
+ self.proto.makeConnection(self.tr)
+
+
+ def _test(self, operation, a, b, expected):
+ d = getattr(self.proto, operation)(a, b)
+ self.assertEqual(self.tr.value(), '%s %d %d\r\n' % (operation, a, b))
+ self.tr.clear()
+ d.addCallback(self.assertEqual, expected)
+ self.proto.dataReceived("%d\r\n" % (expected,))
+ return d
+
+
+ def test_add(self):
+ return self._test('add', 7, 6, 13)
+
+
+ def test_subtract(self):
+ return self._test('subtract', 82, 78, 4)
+
+
+ def test_multiply(self):
+ return self._test('multiply', 2, 8, 16)
+
+
+ def test_divide(self):
+ return self._test('divide', 14, 3, 4)
+
+
+ def test_timeout(self):
+ d = self.proto.add(9, 4)
+ self.assertEqual(self.tr.value(), 'add 9 4\r\n')
+ self.clock.advance(self.proto.timeOut)
+ return self.assertFailure(d, ClientTimeoutError)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_client_3.py b/doc/core/howto/listings/trial/calculus/test/test_client_3.py
new file mode 100644
index 0000000..78ac5e1
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_client_3.py
@@ -0,0 +1,63 @@
+from calculus.client_3 import RemoteCalculationClient, ClientTimeoutError
+
+from twisted.internet import task
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+
+class ClientCalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.tr = proto_helpers.StringTransportWithDisconnection()
+ self.clock = task.Clock()
+ self.proto = RemoteCalculationClient()
+ self.tr.protocol = self.proto
+ self.proto.callLater = self.clock.callLater
+ self.proto.makeConnection(self.tr)
+
+
+ def _test(self, operation, a, b, expected):
+ d = getattr(self.proto, operation)(a, b)
+ self.assertEqual(self.tr.value(), '%s %d %d\r\n' % (operation, a, b))
+ self.tr.clear()
+ d.addCallback(self.assertEqual, expected)
+ self.proto.dataReceived("%d\r\n" % (expected,))
+ return d
+
+
+ def test_add(self):
+ return self._test('add', 7, 6, 13)
+
+
+ def test_subtract(self):
+ return self._test('subtract', 82, 78, 4)
+
+
+ def test_multiply(self):
+ return self._test('multiply', 2, 8, 16)
+
+
+ def test_divide(self):
+ return self._test('divide', 14, 3, 4)
+
+
+ def test_timeout(self):
+ d = self.proto.add(9, 4)
+ self.assertEqual(self.tr.value(), 'add 9 4\r\n')
+ self.clock.advance(self.proto.timeOut)
+ return self.assertFailure(d, ClientTimeoutError)
+
+
+ def test_timeoutConnectionLost(self):
+ called = []
+ def lost(arg):
+ called.append(True)
+ self.proto.connectionLost = lost
+
+ d = self.proto.add(9, 4)
+ self.assertEqual(self.tr.value(), 'add 9 4\r\n')
+ self.clock.advance(self.proto.timeOut)
+
+ def check(ignore):
+ self.assertEqual(called, [True])
+ return self.assertFailure(d, ClientTimeoutError).addCallback(check)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_remote_1.py b/doc/core/howto/listings/trial/calculus/test/test_remote_1.py
new file mode 100644
index 0000000..5f41657
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_remote_1.py
@@ -0,0 +1,34 @@
+from calculus.remote_1 import RemoteCalculationFactory
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+
+class RemoteCalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ factory = RemoteCalculationFactory()
+ self.proto = factory.buildProtocol(('127.0.0.1', 0))
+ self.tr = proto_helpers.StringTransport()
+ self.proto.makeConnection(self.tr)
+
+
+ def _test(self, operation, a, b, expected):
+ self.proto.dataReceived('%s %d %d\r\n' % (operation, a, b))
+ self.assertEqual(int(self.tr.value()), expected)
+
+
+ def test_add(self):
+ return self._test('add', 7, 6, 13)
+
+
+ def test_subtract(self):
+ return self._test('subtract', 82, 78, 4)
+
+
+ def test_multiply(self):
+ return self._test('multiply', 2, 8, 16)
+
+
+ def test_divide(self):
+ return self._test('divide', 14, 3, 4)
+
diff --git a/doc/core/howto/listings/trial/calculus/test/test_remote_2.py b/doc/core/howto/listings/trial/calculus/test/test_remote_2.py
new file mode 100644
index 0000000..75b5011
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_remote_2.py
@@ -0,0 +1,46 @@
+from calculus.remote_1 import RemoteCalculationFactory
+from calculus.client_2 import RemoteCalculationClient
+
+from twisted.trial import unittest
+from twisted.internet import reactor, protocol
+
+
+
+class RemoteRunCalculationTestCase(unittest.TestCase):
+
+ def setUp(self):
+ factory = RemoteCalculationFactory()
+ self.port = reactor.listenTCP(0, factory, interface="127.0.0.1")
+ self.client = None
+
+
+ def tearDown(self):
+ if self.client is not None:
+ self.client.transport.loseConnection()
+ return self.port.stopListening()
+
+
+ def _test(self, op, a, b, expected):
+ creator = protocol.ClientCreator(reactor, RemoteCalculationClient)
+ def cb(client):
+ self.client = client
+ return getattr(self.client, op)(a, b
+ ).addCallback(self.assertEqual, expected)
+ return creator.connectTCP('127.0.0.1', self.port.getHost().port
+ ).addCallback(cb)
+
+
+ def test_add(self):
+ return self._test("add", 5, 9, 14)
+
+
+ def test_subtract(self):
+ return self._test("subtract", 47, 13, 34)
+
+
+ def test_multiply(self):
+ return self._test("multiply", 7, 3, 21)
+
+
+ def test_divide(self):
+ return self._test("divide", 84, 10, 8)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_remote_3.py b/doc/core/howto/listings/trial/calculus/test/test_remote_3.py
new file mode 100644
index 0000000..0f0c555
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_remote_3.py
@@ -0,0 +1,40 @@
+from calculus.remote_2 import RemoteCalculationFactory
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+
+class RemoteCalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ factory = RemoteCalculationFactory()
+ self.proto = factory.buildProtocol(('127.0.0.1', 0))
+ self.tr = proto_helpers.StringTransport()
+ self.proto.makeConnection(self.tr)
+
+
+ def _test(self, operation, a, b, expected):
+ self.proto.dataReceived('%s %d %d\r\n' % (operation, a, b))
+ self.assertEqual(int(self.tr.value()), expected)
+
+
+ def test_add(self):
+ return self._test('add', 7, 6, 13)
+
+
+ def test_subtract(self):
+ return self._test('subtract', 82, 78, 4)
+
+
+ def test_multiply(self):
+ return self._test('multiply', 2, 8, 16)
+
+
+ def test_divide(self):
+ return self._test('divide', 14, 3, 4)
+
+
+ def test_invalidParameters(self):
+ self.proto.dataReceived('add foo bar\r\n')
+ self.assertEqual(self.tr.value(), "error\r\n")
+ errors = self.flushLoggedErrors(TypeError)
+ self.assertEqual(len(errors), 1)
diff --git a/doc/core/howto/listings/udp/MulticastClient.py b/doc/core/howto/listings/udp/MulticastClient.py
new file mode 100644
index 0000000..7a41aaa
--- /dev/null
+++ b/doc/core/howto/listings/udp/MulticastClient.py
@@ -0,0 +1,19 @@
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+
+class MulticastPingClient(DatagramProtocol):
+
+ def startProtocol(self):
+ # Join the multicast address, so we can receive replies:
+ self.transport.joinGroup("228.0.0.5")
+ # Send to 228.0.0.5:8005 - all listeners on the multicast address
+ # (including us) will receive this message.
+ self.transport.write('Client: Ping', ("228.0.0.5", 8005))
+
+ def datagramReceived(self, datagram, address):
+ print "Datagram %s received from %s" % (repr(datagram), repr(address))
+
+
+reactor.listenMulticast(8005, MulticastPingClient(), listenMultiple=True)
+reactor.run()
diff --git a/doc/core/howto/listings/udp/MulticastServer.py b/doc/core/howto/listings/udp/MulticastServer.py
new file mode 100644
index 0000000..0909e60
--- /dev/null
+++ b/doc/core/howto/listings/udp/MulticastServer.py
@@ -0,0 +1,28 @@
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+
+class MulticastPingPong(DatagramProtocol):
+
+ def startProtocol(self):
+ """
+ Called after protocol has started listening.
+ """
+ # Set the TTL>1 so multicast will cross router hops:
+ self.transport.setTTL(5)
+ # Join a specific multicast group:
+ self.transport.joinGroup("228.0.0.5")
+
+ def datagramReceived(self, datagram, address):
+ print "Datagram %s received from %s" % (repr(datagram), repr(address))
+ if datagram == "Client: Ping":
+ # Rather than replying to the group multicast address, we send the
+ # reply directly (unicast) to the originating port:
+ self.transport.write("Server: Pong", address)
+
+
+# We use listenMultiple=True so that we can run MulticastServer.py and
+# MulticastClient.py on same machine:
+reactor.listenMulticast(8005, MulticastPingPong(),
+ listenMultiple=True)
+reactor.run()
diff --git a/doc/core/howto/logging.html b/doc/core/howto/logging.html
new file mode 100644
index 0000000..95f8f0d
--- /dev/null
+++ b/doc/core/howto/logging.html
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Logging with twisted.python.log</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Logging with twisted.python.log</h1>
+ <div class="toc"><ol><li><a href="#auto0">Basic usage</a></li><ul><li><a href="#auto1">Logging and twistd</a></li><li><a href="#auto2">Log files</a></li><li><a href="#auto3">Using the standard library logging module</a></li></ul><li><a href="#auto4">Writing log observers</a></li><li><a href="#auto5">Customizing twistd logging</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Basic usage<a name="auto0"/></h2>
+
+ <p>Twisted provides a simple and flexible logging system in the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.html" title="twisted.python.log">twisted.python.log</a></code> module. It has three commonly used
+ functions:</p>
+
+ <dl>
+ <dt><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.LogPublisher.msg.html" title="twisted.python.log.LogPublisher.msg">msg</a></code></dt>
+ <dd>Logs a new message. For example:
+ <pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-variable">log</span>.<span class="py-src-variable">msg</span>(<span class="py-src-string">'Hello, world.'</span>)
+</pre>
+ </dd>
+
+ <dt><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.err.html" title="twisted.python.log.err">err</a></code></dt>
+ <dd>Writes a failure to the log, including traceback information (if any).
+ You can pass it a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code> or Exception instance, or
+ nothing. If you pass something else, it will be converted to a string
+ with <code>repr</code> and logged.
+
+ If you pass nothing, it will construct a Failure from the
+ currently active exception, which makes it convenient to use in an <code class="python">except</code> clause:
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">x</span> = <span class="py-src-number">1</span> / <span class="py-src-number">0</span>
+<span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>() <span class="py-src-comment"># will log the ZeroDivisionError</span>
+</pre>
+ </dd>
+
+ <dt><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.startLogging.html" title="twisted.python.log.startLogging">startLogging</a></code></dt>
+ <dd>Starts logging to a given file-like object. For example:
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">log</span>.<span class="py-src-variable">startLogging</span>(<span class="py-src-variable">open</span>(<span class="py-src-string">'/var/log/foo.log'</span>, <span class="py-src-string">'w'</span>))
+</pre>
+ or:
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">log</span>.<span class="py-src-variable">startLogging</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">stdout</span>)
+</pre>
+ or:
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">logfile</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DailyLogFile</span>
+
+<span class="py-src-variable">log</span>.<span class="py-src-variable">startLogging</span>(<span class="py-src-variable">DailyLogFile</span>.<span class="py-src-variable">fromFullPath</span>(<span class="py-src-string">&quot;/var/log/foo.log&quot;</span>))
+</pre>
+
+ By default, <code>startLogging</code> will also redirect anything written
+ to <code>sys.stdout</code> and <code>sys.stderr</code> to the log. You
+ can disable this by passing <code class="python">setStdout=False</code> to
+ <code>startLogging</code>.
+ </dd>
+ </dl>
+
+ <p>Before <code>startLogging</code> is called, log messages will be
+ discarded and errors will be written to stderr.</p>
+
+ <h3>Logging and twistd<a name="auto1"/></h3>
+
+ <p>If you are using <code class="shell">twistd</code> to run your daemon, it
+ will take care of calling <code>startLogging</code> for you, and will also
+ rotate log files. See <a href="application.html#twistd" shape="rect">twistd and tac</a>
+ and the <code class="shell">twistd</code> man page for details of using
+ twistd.</p>
+
+ <h3>Log files<a name="auto2"/></h3>
+
+ <p>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.logfile.html" title="twisted.python.logfile">twisted.python.logfile</a></code> module provides
+ some standard classes suitable for use with <code>startLogging</code>, such
+ as <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.logfile.DailyLogFile.html" title="twisted.python.logfile.DailyLogFile">DailyLogFile</a></code>,
+ which will rotate the log to a new file once per day.</p>
+
+ <h3>Using the standard library logging module<a name="auto3"/></h3>
+
+ <p>If your application uses the
+ Python <a href="http://docs.python.org/library/logging.html" shape="rect">standard
+ library logging module</a> or you want to use its easy configuration but
+ don't want to lose twisted-produced messages, the observer
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.PythonLoggingObserver.html" title="twisted.python.log.PythonLoggingObserver">PythonLoggingObserver</a></code>
+ should be useful to you.
+ </p>
+
+ <p>You just start it like any other observer:
+ <pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">observer</span> = <span class="py-src-variable">log</span>.<span class="py-src-variable">PythonLoggingObserver</span>()
+<span class="py-src-variable">observer</span>.<span class="py-src-variable">start</span>()
+</pre>
+
+ Then <a href="http://docs.python.org/library/logging.html" shape="rect">configure the
+ standard library logging module</a> to behave as you want.
+ </p>
+
+ <p>This method allows you to customize the log level received by the
+ standard library logging module using the <code>logLevel</code> keyword:
+ <pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">log</span>.<span class="py-src-variable">msg</span>(<span class="py-src-string">&quot;This is important!&quot;</span>, <span class="py-src-variable">logLevel</span>=<span class="py-src-variable">logging</span>.<span class="py-src-variable">CRITICAL</span>)
+<span class="py-src-variable">log</span>.<span class="py-src-variable">msg</span>(<span class="py-src-string">&quot;Don't mind&quot;</span>, <span class="py-src-variable">logLevel</span>=<span class="py-src-variable">logging</span>.<span class="py-src-variable">DEBUG</span>)
+</pre>
+ Unless <code>logLevel</code> is provided, logging.INFO is used for <code>log.msg</code>
+ and <code>logging.ERROR</code> is used for <code>log.err</code>.
+ </p>
+
+ <p>One special care should be made when you use special configuration of
+ the standard library logging module: some handlers (e.g. SMTP, HTTP) use the network and
+ so can block inside the reactor loop. <em>Nothing</em> in <code>PythonLoggingObserver</code> is
+ done to prevent that.</p>
+
+ <h2>Writing log observers<a name="auto4"/></h2>
+
+ <p>Log observers are the basis of the Twisted logging system.
+ Whenever <code>log.msg</code> (or <code>log.err</code>) is called, an
+ event is emitted. The event is passed to each observer which has been
+ registered. There can be any number of observers, and each can treat
+ the event in any way desired.
+ An example of
+ a log observer in Twisted is the <code>emit</code> method of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.FileLogObserver.html" title="twisted.python.log.FileLogObserver">FileLogObserver</a></code>.
+ <code>FileLogObserver</code>, used by
+ <code>startLogging</code>, writes events to a log file. A log observer
+ is just a callable that accepts a dictionary as its only argument. You can
+ then register it to receive all log events (in addition to any other
+ observers):</p>
+
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span>.<span class="py-src-variable">addObserver</span>(<span class="py-src-variable">yourCallable</span>)
+</pre>
+
+ <p>The dictionary will have at least two items:</p>
+
+ <dl>
+ <dt>message</dt>
+ <dd>The message (a list, usually of strings)
+ for this log event, as passed to <code>log.msg</code> or the
+ message in the failure passed to <code>log.err</code>.</dd>
+
+ <dt>isError</dt>
+ <dd>This is a boolean that will be true if this event came from a call to
+ <code>log.err</code>. If this is set, there may be a <code>failure</code>
+ item in the dictionary as will, with a Failure object in it.</dd>
+ </dl>
+
+ <p>Other items the built in logging functionality may add include:</p>
+
+ <dl>
+ <dt>printed</dt>
+ <dd>This message was captured from <code>sys.stdout</code>, i.e. this
+ message came from a <code>print</code> statement. If
+ <code>isError</code> is also true, it came from
+ <code>sys.stderr</code>.</dd>
+ </dl>
+
+ <p>You can pass additional items to the event dictionary by passing keyword
+ arguments to <code>log.msg</code> and <code>log.err</code>. The standard
+ log observers will ignore dictionary items they don't use.</p>
+
+ <p>Important notes:</p>
+
+ <ul>
+ <li>Never block in a log observer, as it may run in main Twisted thread.
+ This means you can't use socket or syslog standard library logging backends.</li>
+
+ <li>The observer needs to be thread safe if you anticipate using threads
+ in your program.</li>
+ </ul>
+
+ <h2>Customizing <code>twistd</code> logging<a name="auto5"/></h2>
+ <p>
+ The behavior of the logging that <code>twistd</code> does can be
+ customized either with the <code>--logger</code> option or by setting the
+ <code>ILogObserver</code> component on the application object. See the <a href="application.html" shape="rect">Application document</a> for more information.
+ </p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/options.html b/doc/core/howto/options.html
new file mode 100644
index 0000000..f674ad2
--- /dev/null
+++ b/doc/core/howto/options.html
@@ -0,0 +1,581 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Parsing command-lines with usage.Options</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Parsing command-lines with usage.Options</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Boolean Options</a></li><ul><li><a href="#auto2">Inheritance, Or: How I Learned to Stop Worrying and Love
+ the Superclass</a></li></ul><li><a href="#auto3">Parameters</a></li><li><a href="#auto4">Option Subcommands</a></li><li><a href="#auto5">Generic Code For Options</a></li><li><a href="#auto6">Parsing Arguments</a></li><li><a href="#auto7">Post Processing</a></li><li><a href="#auto8">Type enforcement</a></li><li><a href="#auto9">Shell tab-completion</a></li><ul><li><a href="#auto10">Completion metadata</a></li></ul></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Introduction<a name="auto0"/></h2>
+
+ <p>There is frequently a need for programs to parse a UNIX-like
+ command line program: options preceded by <code>-</code> or
+ <code>--</code>, sometimes followed by a parameter, followed by
+ a list of arguments. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.usage.html" title="twisted.python.usage">twisted.python.usage</a></code> provides a class,
+ <code>Options</code>, to facilitate such parsing.</p>
+
+ <p>While Python has the <code>getopt</code> module for doing
+ this, it provides a very low level of abstraction for options.
+ Twisted has a higher level of abstraction, in the class <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.usage.Options.html" title="twisted.python.usage.Options">twisted.python.usage.Options</a></code>. It uses
+ Python's reflection facilities to provide an easy to use yet
+ flexible interface to the command line. While most command line
+ processors either force the application writer to write her own
+ loops, or have arbitrary limitations on the command line (the
+ most common one being not being able to have more then one
+ instance of a specific option, thus rendering the idiom
+ <code class="shell">program -v -v -v</code> impossible), Twisted allows the
+ programmer to decide how much control she wants.</p>
+
+ <p>The <code>Options</code> class is used by subclassing. Since
+ a lot of time it will be used in the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.tap.html" title="twisted.tap">twisted.tap</a></code> package, where the local
+ conventions require the specific options parsing class to also
+ be called <code>Options</code>, it is usually imported with</p>
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+</pre>
+
+ <h2>Boolean Options<a name="auto1"/></h2>
+
+ <p>For simple boolean options, define the attribute
+ <code>optFlags</code> like this:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optFlags</span> = [[<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-string">&quot;Act quickly&quot;</span>], [<span class="py-src-string">&quot;safe&quot;</span>, <span class="py-src-string">&quot;s&quot;</span>, <span class="py-src-string">&quot;Act safely&quot;</span>]]
+</pre>
+ <p><code>optFlags</code> should be a list of 3-lists. The first element
+ is the long name, and will be used on the command line as
+ <code>--fast</code>. The second one is the short name, and will be used
+ on the command line as <code>-f</code>. The last element is a
+ description of the flag and will be used to generate the usage
+ information text. The long name also determines the name of the key
+ that will be set on the Options instance. Its value will be 1 if the
+ option was seen, 0 otherwise. Here is an example for usage:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optFlags</span> = [
+ [<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-string">&quot;Act quickly&quot;</span>],
+ [<span class="py-src-string">&quot;good&quot;</span>, <span class="py-src-string">&quot;g&quot;</span>, <span class="py-src-string">&quot;Act well&quot;</span>],
+ [<span class="py-src-string">&quot;cheap&quot;</span>, <span class="py-src-string">&quot;c&quot;</span>, <span class="py-src-string">&quot;Act cheaply&quot;</span>]
+ ]
+
+<span class="py-src-variable">command_line</span> = [<span class="py-src-string">&quot;-g&quot;</span>, <span class="py-src-string">&quot;--fast&quot;</span>]
+
+<span class="py-src-variable">options</span> = <span class="py-src-variable">Options</span>()
+<span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">options</span>.<span class="py-src-variable">parseOptions</span>(<span class="py-src-variable">command_line</span>)
+<span class="py-src-keyword">except</span> <span class="py-src-variable">usage</span>.<span class="py-src-variable">UsageError</span>, <span class="py-src-variable">errortext</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'%s: %s'</span> % (<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>], <span class="py-src-variable">errortext</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'%s: Try --help for usage details.'</span> % (<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>])
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">exit</span>(<span class="py-src-number">1</span>)
+<span class="py-src-keyword">if</span> <span class="py-src-variable">options</span>[<span class="py-src-string">'fast'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;fast&quot;</span>,
+<span class="py-src-keyword">if</span> <span class="py-src-variable">options</span>[<span class="py-src-string">'good'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;good&quot;</span>,
+<span class="py-src-keyword">if</span> <span class="py-src-variable">options</span>[<span class="py-src-string">'cheap'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;cheap&quot;</span>,
+<span class="py-src-keyword">print</span>
+</pre>
+
+ <p>The above will print <code>fast good</code>.</p>
+
+ <p>Note here that Options fully supports the mapping interface. You can
+ access it mostly just like you can access any other dict. Options are stored
+ as mapping items in the Options instance: parameters as 'paramname': 'value'
+ and flags as 'flagname': 1 or 0.</p>
+
+ <h3>Inheritance, Or: How I Learned to Stop Worrying and Love
+ the Superclass<a name="auto2"/></h3>
+
+ <p>Sometimes there is a need for several option processors with
+ a unifying core. Perhaps you want all your commands to
+ understand <code>-q</code>/<code>--quiet</code> means to be
+ quiet, or something similar. On the face of it, this looks
+ impossible: in Python, the subclass's <code>optFlags</code>
+ would shadow the superclass's. However,
+ <code>usage.Options</code> uses special reflection code to get
+ all of the <code>optFlags</code> defined in the hierarchy. So
+ the following:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">BaseOptions</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optFlags</span> = [[<span class="py-src-string">&quot;quiet&quot;</span>, <span class="py-src-string">&quot;q&quot;</span>, <span class="py-src-variable">None</span>]]
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SpecificOptions</span>(<span class="py-src-parameter">BaseOptions</span>):
+
+ <span class="py-src-variable">optFlags</span> = [
+ [<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-variable">None</span>], [<span class="py-src-string">&quot;good&quot;</span>, <span class="py-src-string">&quot;g&quot;</span>, <span class="py-src-variable">None</span>], [<span class="py-src-string">&quot;cheap&quot;</span>, <span class="py-src-string">&quot;c&quot;</span>, <span class="py-src-variable">None</span>]
+ ]
+</pre>
+ <p>Is the same as: </p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SpecificOptions</span>(<span class="py-src-parameter">BaseOptions</span>):
+
+ <span class="py-src-variable">optFlags</span> = [
+ [<span class="py-src-string">&quot;quiet&quot;</span>, <span class="py-src-string">&quot;q&quot;</span>, <span class="py-src-string">&quot;Silence output&quot;</span>],
+ [<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-string">&quot;Run quickly&quot;</span>],
+ [<span class="py-src-string">&quot;good&quot;</span>, <span class="py-src-string">&quot;g&quot;</span>, <span class="py-src-string">&quot;Don't validate input&quot;</span>],
+ [<span class="py-src-string">&quot;cheap&quot;</span>, <span class="py-src-string">&quot;c&quot;</span>, <span class="py-src-string">&quot;Use cheap resources&quot;</span>]
+ ]
+</pre>
+
+ <h2>Parameters<a name="auto3"/></h2>
+
+ <p>Parameters are specified using the attribute
+ <code>optParameters</code>. They <em>must</em> be given a
+ default. If you want to make sure you got the parameter from
+ the command line, give a non-string default. Since the command
+ line only has strings, this is completely reliable.</p>
+
+ <p>Here is an example:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optFlags</span> = [
+ [<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-string">&quot;Run quickly&quot;</span>],
+ [<span class="py-src-string">&quot;good&quot;</span>, <span class="py-src-string">&quot;g&quot;</span>, <span class="py-src-string">&quot;Don't validate input&quot;</span>],
+ [<span class="py-src-string">&quot;cheap&quot;</span>, <span class="py-src-string">&quot;c&quot;</span>, <span class="py-src-string">&quot;Use cheap resources&quot;</span>]
+ ]
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">&quot;user&quot;</span>, <span class="py-src-string">&quot;u&quot;</span>, <span class="py-src-variable">None</span>, <span class="py-src-string">&quot;The user name&quot;</span>]]
+
+<span class="py-src-variable">config</span> = <span class="py-src-variable">Options</span>()
+<span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">config</span>.<span class="py-src-variable">parseOptions</span>() <span class="py-src-comment"># When given no argument, parses sys.argv[1:]</span>
+<span class="py-src-keyword">except</span> <span class="py-src-variable">usage</span>.<span class="py-src-variable">UsageError</span>, <span class="py-src-variable">errortext</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'%s: %s'</span> % (<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>], <span class="py-src-variable">errortext</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'%s: Try --help for usage details.'</span> % (<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>])
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">exit</span>(<span class="py-src-number">1</span>)
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>[<span class="py-src-string">'user'</span>] <span class="py-src-keyword">is</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">None</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Hello&quot;</span>, <span class="py-src-variable">config</span>[<span class="py-src-string">'user'</span>]
+<span class="py-src-keyword">print</span> <span class="py-src-string">&quot;So, you want it:&quot;</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>[<span class="py-src-string">'fast'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;fast&quot;</span>,
+<span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>[<span class="py-src-string">'good'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;good&quot;</span>,
+<span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>[<span class="py-src-string">'cheap'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;cheap&quot;</span>,
+<span class="py-src-keyword">print</span>
+</pre>
+
+ <p>Like <code>optFlags</code>, <code>optParameters</code> works
+ smoothly with inheritance.</p>
+
+ <h2>Option Subcommands<a name="auto4"/></h2>
+
+ <p>It is useful, on occassion, to group a set of options together based
+ on the logical <q>action</q> to which they belong. For this, the
+ <code>usage.Options</code> class allows you to define a set of
+ <q>subcommands</q>, each of which can provide its own
+ <code>usage.Options</code> instance to handle its particular
+ options.</p>
+
+ <p>Here is an example for an Options class that might parse
+ options like those the cvs program takes</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ImportOptions</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">optParameters</span> = [
+ [<span class="py-src-string">'module'</span>, <span class="py-src-string">'m'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>], [<span class="py-src-string">'vendor'</span>, <span class="py-src-string">'v'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>],
+ [<span class="py-src-string">'release'</span>, <span class="py-src-string">'r'</span>, <span class="py-src-variable">None</span>]
+ ]
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CheckoutOptions</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">'module'</span>, <span class="py-src-string">'m'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>], [<span class="py-src-string">'tag'</span>, <span class="py-src-string">'r'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>]]
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">subCommands</span> = [[<span class="py-src-string">'import'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">ImportOptions</span>, <span class="py-src-string">&quot;Do an Import&quot;</span>],
+ [<span class="py-src-string">'checkout'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">CheckoutOptions</span>, <span class="py-src-string">&quot;Do a Checkout&quot;</span>]]
+
+ <span class="py-src-variable">optParameters</span> = [
+ [<span class="py-src-string">'compression'</span>, <span class="py-src-string">'z'</span>, <span class="py-src-number">0</span>, <span class="py-src-string">'Use compression'</span>],
+ [<span class="py-src-string">'repository'</span>, <span class="py-src-string">'r'</span>, <span class="py-src-variable">None</span>, <span class="py-src-string">'Specify an alternate repository'</span>]
+ ]
+
+<span class="py-src-variable">config</span> = <span class="py-src-variable">Options</span>(); <span class="py-src-variable">config</span>.<span class="py-src-variable">parseOptions</span>()
+<span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">subCommand</span> == <span class="py-src-string">'import'</span>:
+ <span class="py-src-variable">doImport</span>(<span class="py-src-variable">config</span>.<span class="py-src-variable">subOptions</span>)
+<span class="py-src-keyword">elif</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">subCommand</span> == <span class="py-src-string">'checkout'</span>:
+ <span class="py-src-variable">doCheckout</span>(<span class="py-src-variable">config</span>.<span class="py-src-variable">subOptions</span>)
+</pre>
+
+ <p>The <code>subCommands</code> attribute of <code>Options</code>
+ directs the parser to the two other <code>Options</code> subclasses
+ when the strings <code>&quot;import&quot;</code> or <code>&quot;checkout&quot;</code> are
+ present on the command
+ line. All options after the given command string are passed to the
+ specified Options subclass for further parsing. Only one subcommand
+ may be specified at a time. After parsing has completed, the Options
+ instance has two new attributes - <code>subCommand</code> and <code>
+ subOptions</code> - which hold the command string and the Options
+ instance used to parse the remaining options.</p>
+
+ <h2>Generic Code For Options<a name="auto5"/></h2>
+
+ <p>Sometimes, just setting an attribute on the basis of the
+ options is not flexible enough. In those cases, Twisted does
+ not even attempt to provide abstractions such as <q>counts</q> or
+ <q>lists</q>, but rathers lets you call your own method, which will
+ be called whenever the option is encountered.</p>
+
+ <p>Here is an example of counting verbosity</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">usage</span>.<span class="py-src-variable">Options</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'verbosity'</span>] = <span class="py-src-number">0</span> <span class="py-src-comment"># default</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">opt_verbose</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'verbosity'</span>] = <span class="py-src-variable">self</span>[<span class="py-src-string">'verbosity'</span>]+<span class="py-src-number">1</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">opt_quiet</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'verbosity'</span>] = <span class="py-src-variable">self</span>[<span class="py-src-string">'verbosity'</span>]-<span class="py-src-number">1</span>
+
+ <span class="py-src-variable">opt_v</span> = <span class="py-src-variable">opt_verbose</span>
+ <span class="py-src-variable">opt_q</span> = <span class="py-src-variable">opt_quiet</span>
+</pre>
+
+ <p>Command lines that look like
+ <code class="shell">command -v -v -v -v</code> will
+ increase verbosity to 4, while
+ <code class="shell">command -q -q -q</code> will decrease
+ verbosity to -3.
+ </p>
+
+ <p>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.usage.Options.html" title="twisted.python.usage.Options">usage.Options</a></code>
+ class knows that these are
+ parameter-less options, since the methods do not receive an
+ argument. Here is an example for a method with a parameter:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">usage</span>.<span class="py-src-variable">Options</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'symbols'</span>] = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">opt_define</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">symbol</span>):
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'symbols'</span>].<span class="py-src-variable">append</span>(<span class="py-src-variable">symbol</span>)
+
+ <span class="py-src-variable">opt_D</span> = <span class="py-src-variable">opt_define</span>
+</pre>
+
+ <p>This example is useful for the common idiom of having
+ <code>command -DFOO -DBAR</code> to define symbols.</p>
+
+ <h2>Parsing Arguments<a name="auto6"/></h2>
+
+ <p><code>usage.Options</code> does not stop helping when the
+ last parameter is gone. All the other arguments are sent into a
+ function which should deal with them. Here is an example for a
+ <code>cmp</code> like command.</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">&quot;max_differences&quot;</span>, <span class="py-src-string">&quot;d&quot;</span>, <span class="py-src-number">1</span>, <span class="py-src-variable">None</span>]]
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">parseArgs</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">origin</span>, <span class="py-src-parameter">changed</span>):
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'origin'</span>] = <span class="py-src-variable">origin</span>
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'changed'</span>] = <span class="py-src-variable">changed</span>
+</pre>
+
+ <p>The command should look like <code>command origin
+ changed</code>.</p>
+
+ <p>If you want to have a variable number of left-over
+ arguments, just use <code>def parseArgs(self, *args):</code>.
+ This is useful for commands like the UNIX
+ <code>cat(1)</code>.</p>
+
+ <h2>Post Processing<a name="auto7"/></h2>
+
+ <p>Sometimes, you want to perform post processing of options to
+ patch up inconsistencies, and the like. Here is an example:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optFlags</span> = [
+ [<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-string">&quot;Run quickly&quot;</span>],
+ [<span class="py-src-string">&quot;good&quot;</span>, <span class="py-src-string">&quot;g&quot;</span>, <span class="py-src-string">&quot;Don't validate input&quot;</span>],
+ [<span class="py-src-string">&quot;cheap&quot;</span>, <span class="py-src-string">&quot;c&quot;</span>, <span class="py-src-string">&quot;Use cheap resources&quot;</span>]
+ ]
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">postOptions</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>[<span class="py-src-string">'fast'</span>] <span class="py-src-keyword">and</span> <span class="py-src-variable">self</span>[<span class="py-src-string">'good'</span>] <span class="py-src-keyword">and</span> <span class="py-src-variable">self</span>[<span class="py-src-string">'cheap'</span>]:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">usage</span>.<span class="py-src-variable">UsageError</span>, <span class="py-src-string">&quot;can't have it all, brother&quot;</span>
+</pre>
+
+ <h2>Type enforcement<a name="auto8"/></h2>
+
+ <p>By default, all options are handled as strings. You may want to
+ enforce the type of your option in some specific case, the classic example
+ being port number. Any callable can be specified in the fifth row of
+ <code>optParameters</code> and will be called with the string value passed
+ in parameter.
+ </p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">optParameters</span> = [
+ [<span class="py-src-string">&quot;shiny_integer&quot;</span>, <span class="py-src-string">&quot;s&quot;</span>, <span class="py-src-number">1</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">int</span>],
+ [<span class="py-src-string">&quot;dummy_float&quot;</span>, <span class="py-src-string">&quot;d&quot;</span>, <span class="py-src-number">3.14159</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">float</span>],
+ ]
+</pre>
+
+ <p>Note that default values are not coerced, so you should either declare
+ it with the good type (as above) or handle it when you use your
+ options.</p>
+
+ <p>The coerce function may have a coerceDoc attribute, the content of which
+ will be printed after the documentation of the option. It's particularly
+ useful for reusing the function at multiple places.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">oneTwoThree</span>(<span class="py-src-parameter">val</span>):
+ <span class="py-src-variable">val</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">val</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">val</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">range</span>(<span class="py-src-number">1</span>, <span class="py-src-number">4</span>):
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">ValueError</span>(<span class="py-src-string">&quot;Not in range&quot;</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">val</span>
+<span class="py-src-variable">oneTwoThree</span>.<span class="py-src-variable">coerceDoc</span> = <span class="py-src-string">&quot;Must be 1, 2 or 3.&quot;</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">&quot;one_choice&quot;</span>, <span class="py-src-string">&quot;o&quot;</span>, <span class="py-src-number">1</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">oneTwoThree</span>]]
+</pre>
+
+<p>This example code will print the following help when added to your program:
+</p>
+
+<pre class="shell" xml:space="preserve">
+$ python myprogram.py --help
+Usage: myprogram [options]
+Options:
+ -o, --one_choice= [default: 0]. Must be 1, 2 or 3.
+</pre>
+ <h2>Shell tab-completion<a name="auto9"/></h2>
+
+ <p>The <code>Options</code> class may provide tab-completion to interactive
+ command shells. Only <code>zsh</code> is supported at present, but there is
+ some interest in supporting <code>bash</code> in the future.</p>
+
+ <p>Support is automatic for all of the commands shipped with Twisted. Zsh
+ has shipped, for a number of years, a completion function which ties in to
+ the support provided by the <code>Options</code> class.</p>
+
+ <p>If you are writing a <code>twistd</code> plugin, then tab-completion
+ for your <code>twistd</code> sub-command is also automatic.</p>
+
+ <p>For other commands you may easily provide zsh tab-completion support.
+ Copy the file &quot;twisted/python/twisted-completion.zsh&quot; and name it something
+ like &quot;_mycommand&quot;. A leading underscore with no extension is zsh's
+ convention for completion function files.</p>
+
+ <p>Edit the new file and change the first line to refer only to your new
+ command(s), like so:</p>
+
+<pre class="shell" xml:space="preserve">
+#compdef mycommand
+</pre>
+
+ <p>Then ensure this file is made available to the shell by placing it in
+ one of the directories appearing in zsh's $fpath. Restart zsh, and ensure
+ advanced completion is enabled
+ (<code>autoload -U compinit; compinit)</code>. You should then be able to
+ type the name of your command and press Tab to have your command-line
+ options completed.</p>
+
+ <h3>Completion metadata<a name="auto10"/></h3>
+
+ <p>Optionally, a special attribute, <code>compData</code>, may be defined
+ on your <code>Options</code> subclass in order to provide more information
+ to the shell-completion system. The attribute should be an instance of
+ <a class="API" shape="rect"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.usage.Completions.html" title="twisted.python.usage.Completions">Completions</a></a>. See that class
+ for further details.</p>
+
+ <p>In addition, <code>compData</code> may be defined on parent classes in
+ your inheritance hiearchy. The information from each
+ <a class="API" shape="rect"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.usage.Completions.html" title="twisted.python.usage.Completions">Completions</a></a> instance will be
+ aggregated when producing the final tab-completion results.</p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/pb-clients.html b/doc/core/howto/pb-clients.html
new file mode 100644
index 0000000..ae5445e
--- /dev/null
+++ b/doc/core/howto/pb-clients.html
@@ -0,0 +1,362 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Managing Clients of Perspectives</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ <link href="http://twistedmatrix.com/users/acapnotic/" rel="author" title="Kevin Turner"/></head>
+
+ <body bgcolor="white">
+ <h1 class="title">Managing Clients of Perspectives</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Managing Avatars</a></li><li><a href="#auto2">Managing Clients</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Overview<a name="auto0"/></h2>
+
+<p>In all the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.IPerspective.html" title="twisted.spread.pb.IPerspective">IPerspective</a></code> uses
+we have shown so far, we ignored the <code>mind</code> argument and created
+a new <code>Avatar</code> for every connection. This is usually an easy
+design choice, and it works well for simple cases.</p>
+
+<p>In more complicated cases, for example an <code>Avatar</code> that
+represents a player object which is persistent in the game universe,
+we will want connections from the same player to use the same
+<code>Avatar</code>.</p>
+
+<p>Another thing which is necessary in more complicated scenarios
+is notifying a player asynchronously. While it is possible, of
+course, to allow a player to call
+<code>perspective_remoteListener(referencable)</code> that would
+mean both duplication of code and a higher latency in logging in,
+both bad.</p>
+
+<p>In previous sections all realms looked to be identical.
+In this one we will show the usefulness of realms in accomplishing
+those two objectives.</p>
+
+<h2>Managing Avatars<a name="auto1"/></h2>
+
+<p>The simplest way to manage persistent avatars is to use a straight-forward
+caching mechanism:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleAvatar</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-variable">greetings</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_greet</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;%d&gt;hello %s&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CachingRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>: <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarId</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>]
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>] = <span class="py-src-variable">SimpleAvatar</span>(<span class="py-src-variable">avatarId</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">p</span>, <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+</pre>
+
+<p>This gives us a perspective which counts the number of greetings it
+sent its client. Implementing a caching strategy, as opposed to generating
+a realm with the correct avatars already in it, is usually easier. This
+makes adding new checkers to the portal, or adding new users to a checker
+database, transparent. Otherwise, careful synchronization is needed between
+the checker and avatar is needed (much like the synchronization between
+UNIX's <code>/etc/shadow</code> and <code>/etc/passwd</code>).</p>
+
+<p>Sometimes, however, an avatar will need enough per-connection state
+that it would be easier to generate a new avatar and cache something
+else. Here is an example of that:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Greeter</span>:
+ <span class="py-src-variable">greetings</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">hello</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;%d&gt;hello&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleAvatar</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">greeter</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greeter</span> = <span class="py-src-variable">greeter</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_greet</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">greeter</span>.<span class="py-src-variable">hello</span>()+<span class="py-src-string">' '</span>+<span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CachingRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greeters</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>: <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarId</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">greeters</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">greeters</span>[<span class="py-src-variable">avatarId</span>]
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">greeters</span>[<span class="py-src-variable">avatarId</span>] = <span class="py-src-variable">Greeter</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">SimpleAvatar</span>(<span class="py-src-variable">avatarId</span>, <span class="py-src-variable">p</span>), <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+</pre>
+
+<p>It might seem tempting to use this pattern to have an avatar which
+is notified of new connections. However, the problems here are twofold:
+it would lead to a thin class which needs to forward all of its methods,
+and it would be impossible to know when disconnections occur. Luckily,
+there is a better pattern:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleAvatar</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-variable">greetings</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">connections</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connect</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">connections</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">disconnect</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">connections</span> -= <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_greet</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;%d&gt;hello %s&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CachingRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>: <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarId</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>]
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>] = <span class="py-src-variable">SimpleAvatar</span>(<span class="py-src-variable">avatarId</span>)
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">connect</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">p</span>, <span class="py-src-variable">p</span>.<span class="py-src-variable">disconnect</span>
+</pre>
+
+<p>It is possible to use such a pattern to define an arbitrary limit for
+the number of concurrent connections:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleAvatar</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-variable">greetings</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">connections</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connect</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">connections</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">disconnect</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">connections</span> -= <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_greet</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;%d&gt;hello %s&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CachingRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">max</span>=<span class="py-src-number">1</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">max</span> = <span class="py-src-variable">max</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>: <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarId</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>]
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>] = <span class="py-src-variable">SimpleAvatar</span>(<span class="py-src-variable">avatarId</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">p</span>.<span class="py-src-variable">connections</span> &gt;= <span class="py-src-variable">self</span>.<span class="py-src-variable">max</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">ValueError</span>(<span class="py-src-string">&quot;too many connections&quot;</span>)
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">connect</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">p</span>, <span class="py-src-variable">p</span>.<span class="py-src-variable">disconnect</span>
+</pre>
+
+<h2>Managing Clients<a name="auto2"/></h2>
+
+<p>So far, all our realms have ignored the <code>mind</code> argument.
+In the case of PB, the <code>mind</code> is an object supplied by
+the remote login method -- usually, when it passes over the wire,
+it becomes a <code>pb.RemoteReference</code>. This object allows
+sending messages to the client as soon as the connection is established
+and authenticated.</p>
+
+<p>Here is a simple remote-clock application which shows the usefulness
+of the <code>mind</code> argument:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleAvatar</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">client</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TimerService</span>(<span class="py-src-number">1</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">telltime</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span>.<span class="py-src-variable">startService</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">client</span> = <span class="py-src-variable">client</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">telltime</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">client</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;notifyTime&quot;</span>, <span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>())
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_setperiod</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">period</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span>.<span class="py-src-variable">stopService</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TimerService</span>(<span class="py-src-variable">period</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">telltime</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span>.<span class="py-src-variable">startService</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">logout</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span>.<span class="py-src-variable">stopService</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Realm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>: <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">SimpleAvatar</span>(<span class="py-src-variable">mind</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">p</span>, <span class="py-src-variable">p</span>.<span class="py-src-variable">logout</span>
+</pre>
+
+<p>In more complicated situations, you might want to cache the avatars
+and give each one a set of <q>current clients</q> or something similar.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/pb-copyable.html b/doc/core/howto/pb-copyable.html
new file mode 100644
index 0000000..3e3f0e4
--- /dev/null
+++ b/doc/core/howto/pb-copyable.html
@@ -0,0 +1,1185 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: PB Copyable: Passing Complex Types</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">PB Copyable: Passing Complex Types</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Motivation</a></li><li><a href="#auto2">Passing Objects</a></li><ul><li><a href="#auto3">Security Options</a></li><li><a href="#auto4">What class to use?</a></li></ul><li><a href="#auto5">pb.Copyable</a></li><ul><li><a href="#auto6">Controlling the Copied State</a></li><li><a href="#auto7">Things To Watch Out For</a></li><li><a href="#auto8">More Information</a></li></ul><li><a href="#auto9">pb.Cacheable</a></li><ul><li><a href="#auto10">Example</a></li><li><a href="#auto11">More Information</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Overview<a name="auto0"/></h2>
+
+<p>This chapter focuses on how to use PB to pass complex types (specifically
+class instances) to and from a remote process. The first section is on
+simply copying the contents of an object to a remote process (<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>). The second covers how
+to copy those contents once, then update them later when they change (<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">Cacheable</a></code>).</p>
+
+<h2>Motivation<a name="auto1"/></h2>
+
+<p>From the <a href="pb-usage.html" shape="rect">previous chapter</a>, you've seen how to
+pass basic types to a remote process, by using them in the arguments or
+return values of a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.callRemote.html" title="twisted.spread.pb.RemoteReference.callRemote">callRemote</a></code> function. However,
+if you've experimented with it, you may have discovered problems when trying
+to pass anything more complicated than a primitive int/list/dict/string
+type, or another <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code> object. At some point you want
+to pass entire objects between processes, instead of having to reduce them
+down to dictionaries on one end and then re-instantiating them on the
+other.</p>
+
+<h2>Passing Objects<a name="auto2"/></h2>
+
+<p>The most obvious and straightforward way to send an object to a remote
+process is with something like the following code. It also happens that this
+code doesn't work, as will be explained below.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">LilyPond</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">frogs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">frogs</span> = <span class="py-src-variable">frogs</span>
+
+<span class="py-src-variable">pond</span> = <span class="py-src-variable">LilyPond</span>(<span class="py-src-number">12</span>)
+<span class="py-src-variable">ref</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;sendPond&quot;</span>, <span class="py-src-variable">pond</span>)
+</pre>
+
+<p>If you try to run this, you might hope that a suitable remote end which
+implements the <code>remote_sendPond</code> method would see that method get
+invoked with an instance from the <code>LilyPond</code> class. But instead,
+you'll encounter the dreaded <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.InsecureJelly.html" title="twisted.spread.jelly.InsecureJelly">InsecureJelly</a></code> exception. This is
+Twisted's way of telling you that you've violated a security restriction,
+and that the receiving end refuses to accept your object.</p>
+
+<h3>Security Options<a name="auto3"/></h3>
+
+<p>What's the big deal? What's wrong with just copying a class into another
+process' namespace?</p>
+
+<p>Reversing the question might make it easier to see the issue: what is the
+problem with accepting a stranger's request to create an arbitrary object in
+your local namespace? The real question is how much power you are granting
+them: what actions can they convince you to take on the basis of the bytes
+they are sending you over that remote connection.</p>
+
+<p>Objects generally represent more power than basic types like strings and
+dictionaries because they also contain (or reference) code, which can modify
+other data structures when executed. Once previously-trusted data is
+subverted, the rest of the program is compromised.</p>
+
+<p>The built-in Python <q>batteries included</q> classes are relatively
+tame, but you still wouldn't want to let a foreign program use them to
+create arbitrary objects in your namespace or on your computer. Imagine a
+protocol that involved sending a file-like object with a <code>read()</code>
+method that was supposed to used later to retrieve a document. Then imagine
+what if that object were created with
+ <code>os.fdopen(&quot;~/.gnupg/secring.gpg&quot;)</code>. Or an instance of
+ <code>telnetlib.Telnet(&quot;localhost&quot;, &quot;chargen&quot;)</code>. </p>
+
+<p>Classes you've written for your own program are likely to have far more
+power. They may run code during <code>__init__</code>, or even have special
+meaning simply because of their existence. A program might have
+ <code>User</code> objects to represent user accounts, and have a rule that
+says all <code>User</code> objects in the system are referenced when
+authorizing a login session. (In this system, <code>User.__init__</code>
+would probably add the object to a global list of known users). The simple
+act of creating an object would give access to somebody. If you could be
+tricked into creating a bad object, an unauthorized user would get
+access.</p>
+
+<p>So object creation needs to be part of a system's security design. The
+dotted line between <q>trusted inside</q> and <q>untrusted outside</q> needs
+to describe what may be done in response to outside events. One of those
+events is the receipt of an object through a PB remote procedure call, which
+is a request to create an object in your <q>inside</q> namespace. The
+question is what to do in response to it. For this reason, you must
+explicitly specify what remote classes will be accepted, and how their
+local representatives are to be created.</p>
+
+<h3>What class to use?<a name="auto4"/></h3>
+
+<p>Another basic question to answer before we can do anything useful with an
+incoming serialized object is: what class should we create? The simplistic
+answer is to create the <q>same kind</q> that was serialized on the sender's
+end of the wire, but this is not as easy or as straightforward as you might
+think. Remember that the request is coming from a different program, using a
+potentially different set of class libraries. In fact, since PB has also
+been implemented in Java, Emacs-Lisp, and other languages, there's no
+guarantee that the sender is even running Python! All we know on the
+receiving end is a list of two things which describe the instance they are
+trying to send us: the name of the class, and a representation of the
+contents of the object.</p>
+
+
+<p>PB lets you specify the mapping from remote class names to local classes
+with the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.setUnjellyableForClass.html" title="twisted.spread.jelly.setUnjellyableForClass">setUnjellyableForClass</a></code> function
+<a href="#footnote-1" title="Note that, in this context, unjelly is a verb with the opposite meaning of jelly. The verb to jelly means to serialize an object or data structure into a sequence of bytes (or other primitive transmittable/storable representation), while to unjelly means to unserialize the bytestream into a live object in the receiver's memory space. Unjellyable is a noun, (not an adjective), referring to the the class that serves as a destination or recipient of the unjellying process. A is unjellyable into B means that a serialized representation A (of some remote object) can be unserialized into a local object of type B. It is these objects B that are the Unjellyable second argument of the setUnjellyableForClass function. In particular, unjellyable does not mean cannot be jellied. Unpersistable means not persistable, but unjelly, unserialize, and unpickle mean to reverse the operations of jellying, serializing, and pickling."><super>1</super></a>.
+
+
+This function takes a remote/sender class reference (either the
+fully-qualified name as used by the sending end, or a class object from
+which the name can be extracted), and a local/recipient class (used to
+create the local representation for incoming serialized objects). Whenever
+the remote end sends an object, the class name that they transmit is looked
+up in the table controlled by this function. If a matching class is found,
+it is used to create the local object. If not, you get the
+ <code>InsecureJelly</code> exception.</p>
+
+<p>In general you expect both ends to share the same codebase: either you
+control the program that is running on both ends of the wire, or both
+programs share some kind of common language that is implemented in code
+which exists on both ends. You wouldn't expect them to send you an object of
+the MyFooziWhatZit class unless you also had a definition for that class. So
+it is reasonable for the Jelly layer to reject all incoming classes except
+the ones that you have explicitly marked with
+ <code>setUnjellyableForClass</code>. But keep in mind that the sender's idea
+of a <code>User</code> object might differ from the recipient's, either
+through namespace collisions between unrelated packages, version skew
+between nodes that haven't been updated at the same rate, or a malicious
+intruder trying to cause your code to fail in some interesting or
+potentially vulnerable way.</p>
+
+
+<h2>pb.Copyable<a name="auto5"/></h2>
+
+<p>Ok, enough of this theory. How do you send a fully-fledged object from
+one side to the other?</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LilyPond</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setStuff</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">color</span>, <span class="py-src-parameter">numFrogs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">color</span> = <span class="py-src-variable">color</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span> = <span class="py-src-variable">numFrogs</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">countFrogs</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;%d frogs&quot;</span> % <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CopyPond</span>(<span class="py-src-parameter">LilyPond</span>, <span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Copyable</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Sender</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">remote</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">remote</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;takePond&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">ok</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">notOk</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">ok</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;pond arrived&quot;</span>, <span class="py-src-variable">response</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">notOk</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;error during takePond:&quot;</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">failure</span>.<span class="py-src-variable">type</span> == <span class="py-src-variable">jelly</span>.<span class="py-src-variable">InsecureJelly</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; InsecureJelly&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">failure</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">copy_sender</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">CopyPond</span> <span class="py-src-comment"># so it's not __main__.CopyPond</span>
+ <span class="py-src-variable">pond</span> = <span class="py-src-variable">CopyPond</span>()
+ <span class="py-src-variable">pond</span>.<span class="py-src-variable">setStuff</span>(<span class="py-src-string">&quot;green&quot;</span>, <span class="py-src-number">7</span>)
+ <span class="py-src-variable">pond</span>.<span class="py-src-variable">countFrogs</span>()
+ <span class="py-src-comment"># class name:</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;.&quot;</span>.<span class="py-src-variable">join</span>([<span class="py-src-variable">pond</span>.<span class="py-src-variable">__class__</span>.<span class="py-src-variable">__module__</span>, <span class="py-src-variable">pond</span>.<span class="py-src-variable">__class__</span>.<span class="py-src-variable">__name__</span>])
+
+ <span class="py-src-variable">sender</span> = <span class="py-src-variable">Sender</span>(<span class="py-src-variable">pond</span>)
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">sender</span>.<span class="py-src-variable">got_obj</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/copy_sender.py"><span class="filename">listings/pb/copy_sender.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+</p><span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+PB copy receiver example.
+
+This is a Twisted Application Configuration (tac) file. Run with e.g.
+ twistd -ny copy_receiver.tac
+
+See the twistd(1) man page or
+http://twistedmatrix.com/documents/current/howto/application for details.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">__doc__</span>
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">exit</span>(<span class="py-src-number">1</span>)
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">copy_sender</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LilyPond</span>, <span class="py-src-variable">CopyPond</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-comment">#log.startLogging(sys.stdout)</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ReceiverPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">RemoteCopy</span>, <span class="py-src-parameter">LilyPond</span>):
+ <span class="py-src-keyword">pass</span>
+<span class="py-src-variable">pb</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">CopyPond</span>, <span class="py-src-variable">ReceiverPond</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Receiver</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takePond</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; got pond:&quot;</span>, <span class="py-src-variable">pond</span>
+ <span class="py-src-variable">pond</span>.<span class="py-src-variable">countFrogs</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;safe and sound&quot;</span> <span class="py-src-comment"># positive acknowledgement</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;copy_receiver&quot;</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Receiver</span>())).<span class="py-src-variable">setServiceParent</span>(
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/pb/copy_receiver.tac"><span class="filename">listings/pb/copy_receiver.tac</span></a></div></div>
+
+<p>The sending side has a class called <code>LilyPond</code>. To make this
+eligble for transport through <code>callRemote</code> (either as an
+argument, a return value, or something referenced by either of those [like a
+dictionary value]), it must inherit from one of the four <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Serializable.html" title="twisted.spread.pb.Serializable">Serializable</a></code> classes. In this section,
+we focus on <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">Copyable</a></code>.
+The copyable subclass of <code>LilyPond</code> is called
+ <code>CopyPond</code>. We create an instance of it and send it through
+ <code>callRemote</code> as an argument to the receiver's
+ <code>remote_takePond</code> method. The Jelly layer will serialize
+(<q>jelly</q>) that object as an instance with a class name of
+<q>copy_sender.CopyPond</q> and some chunk of data that represents the
+object's state. <code>pond.__class__.__module__</code> and
+ <code>pond.__class__.__name__</code> are used to derive the class name
+string. The object's <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.Copyable.getStateToCopy.html" title="twisted.spread.flavors.Copyable.getStateToCopy">getStateToCopy</a></code> method is
+used to get the state: this is provided by <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>, and the default just retrieves
+ <code>self.__dict__</code>. This works just like the optional
+ <code>__getstate__</code> method used by <code>pickle</code>. The pair of
+name and state are sent over the wire to the receiver.</p>
+
+<p>The receiving end defines a local class named <code>ReceiverPond</code>
+to represent incoming <code>LilyPond</code> instances. This class derives
+from the sender's <code>LilyPond</code> class (with a fully-qualified name
+of <code>copy_sender.LilyPond</code>), which specifies how we expect it to
+behave. We trust that this is the same <code>LilyPond</code> class as the
+sender used. (At the very least, we hope ours will be able to accept a state
+created by theirs). It also inherits from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code>, which is a requirement for all
+classes that act in this local-representative role (those which are given to
+the second argument of <code>setUnjellyableForClass</code>).
+ <code>RemoteCopy</code> provides the methods that tell the Jelly layer how
+to create the local object from the incoming serialized state.</p>
+
+<p>Then <code>setUnjellyableForClass</code> is used to register the two
+classes. This has two effects: instances of the remote class (the first
+argument) will be allowed in through the security layer, and instances of
+the local class (the second argument) will be used to contain the state that
+is transmitted when the sender serializes the remote object.</p>
+
+<p>When the receiver unserializes (<q>unjellies</q>) the object, it will
+create an instance of the local <code>ReceiverPond</code> class, and hand
+the transmitted state (usually in the form of a dictionary) to that object's
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.RemoteCopy.setCopyableState.html" title="twisted.spread.flavors.RemoteCopy.setCopyableState">setCopyableState</a></code> method.
+This acts just like the <code>__setstate__</code> method that
+ <code>pickle</code> uses when unserializing an object.
+ <code>getStateToCopy</code>/<code>setCopyableState</code> are distinct from
+ <code>__getstate__</code>/<code>__setstate__</code> to allow objects to be
+persisted (across time) differently than they are transmitted (across
+[memory]space).</p>
+
+<p>When this is run, it produces the following output:</p>
+
+<pre class="shell" xml:space="preserve">
+[-] twisted.spread.pb.PBServerFactory starting on 8800
+[-] Starting factory &lt;twisted.spread.pb.PBServerFactory instance at
+0x406159cc&gt;
+[Broker,0,127.0.0.1] got pond: &lt;__builtin__.ReceiverPond instance at
+0x406ec5ec&gt;
+[Broker,0,127.0.0.1] 7 frogs
+</pre>
+
+<pre class="shell" xml:space="preserve">
+$ ./copy_sender.py
+7 frogs
+copy_sender.CopyPond
+pond arrived safe and sound
+Main loop terminated.
+$
+</pre>
+
+
+
+<h3>Controlling the Copied State<a name="auto6"/></h3>
+
+<p>By overriding <code>getStateToCopy</code> and
+ <code>setCopyableState</code>, you can control how the object is transmitted
+over the wire. For example, you might want perform some data-reduction:
+pre-compute some results instead of sending all the raw data over the wire.
+Or you could replace references to a local object on the sender's side with
+markers before sending, then upon receipt replace those markers with
+references to a receiver-side proxy that could perform the same operations
+against a local cache of data.</p>
+
+<p>Another good use for <code>getStateToCopy</code> is to implement
+<q>local-only</q> attributes: data that is only accessible by the local
+process, not to any remote users. For example, a <code>.password</code>
+attribute could be removed from the object state before sending to a remote
+system. Combined with the fact that <code>Copyable</code> objects return
+unchanged from a round trip, this could be used to build a
+challenge-response system (in fact PB does this with
+ <code>pb.Referenceable</code> objects to implement authorization as
+described <a href="pb-cred.html" shape="rect">here</a>).</p>
+
+<p>Whatever <code>getStateToCopy</code> returns from the sending object will
+be serialized and sent over the wire; <code>setCopyableState</code> gets
+whatever comes over the wire and is responsible for setting up the state of
+the object it lives in.</p>
+
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FrogPond</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">numFrogs</span>, <span class="py-src-parameter">numToads</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span> = <span class="py-src-variable">numFrogs</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">numToads</span> = <span class="py-src-variable">numToads</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span> + <span class="py-src-variable">self</span>.<span class="py-src-variable">numToads</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SenderPond</span>(<span class="py-src-parameter">FrogPond</span>, <span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Copyable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getStateToCopy</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">__dict__</span>.<span class="py-src-variable">copy</span>()
+ <span class="py-src-variable">d</span>[<span class="py-src-string">'frogsAndToads'</span>] = <span class="py-src-variable">d</span>[<span class="py-src-string">'numFrogs'</span>] + <span class="py-src-variable">d</span>[<span class="py-src-string">'numToads'</span>]
+ <span class="py-src-keyword">del</span> <span class="py-src-variable">d</span>[<span class="py-src-string">'numFrogs'</span>]
+ <span class="py-src-keyword">del</span> <span class="py-src-variable">d</span>[<span class="py-src-string">'numToads'</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ReceiverPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">RemoteCopy</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setCopyableState</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">state</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">__dict__</span> = <span class="py-src-variable">state</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">frogsAndToads</span>
+
+<span class="py-src-variable">pb</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">SenderPond</span>, <span class="py-src-variable">ReceiverPond</span>)
+</pre><div class="caption">Source listing - <a href="listings/pb/copy2_classes.py"><span class="filename">listings/pb/copy2_classes.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">copy2_classes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SenderPond</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Sender</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">obj</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">obj</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;takePond&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">ok</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">notOk</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">ok</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;pond arrived&quot;</span>, <span class="py-src-variable">response</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">notOk</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;error during takePond:&quot;</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">failure</span>.<span class="py-src-variable">type</span> == <span class="py-src-variable">jelly</span>.<span class="py-src-variable">InsecureJelly</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; InsecureJelly&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">failure</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">pond</span> = <span class="py-src-variable">SenderPond</span>(<span class="py-src-number">3</span>, <span class="py-src-number">4</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;count %d&quot;</span> % <span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
+
+ <span class="py-src-variable">sender</span> = <span class="py-src-variable">Sender</span>(<span class="py-src-variable">pond</span>)
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">sender</span>.<span class="py-src-variable">got_obj</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/copy2_sender.py"><span class="filename">listings/pb/copy2_sender.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">copy2_classes</span> <span class="py-src-comment"># needed to get ReceiverPond registered with Jelly</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Receiver</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takePond</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; got pond:&quot;</span>, <span class="py-src-variable">pond</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; count %d&quot;</span> % <span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;safe and sound&quot;</span> <span class="py-src-comment"># positive acknowledgement</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;copy_receiver&quot;</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Receiver</span>())).<span class="py-src-variable">setServiceParent</span>(
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/pb/copy2_receiver.py"><span class="filename">listings/pb/copy2_receiver.py</span></a></div></div>
+
+<p>In this example, the classes are defined in a separate source file, which
+also sets up the binding between them. The <code>SenderPond</code> and
+<code>ReceiverPond</code> are unrelated save for this binding: they happen
+to implement the same methods, but use different internal instance variables
+to accomplish them.</p>
+
+<p>The recipient of the object doesn't even have to import the class
+definition into their namespace. It is sufficient that they import the class
+definition (and thus execute the <code>setUnjellyableForClass</code>
+statement). The Jelly layer remembers the class definition until a matching
+object is received. The sender of the object needs the definition, of
+course, to create the object in the first place.</p>
+
+<p>When run, the <code>copy2</code> example emits the following:</p>
+
+<pre class="shell" xml:space="preserve">
+$ twistd -n -y copy2_receiver.py
+[-] twisted.spread.pb.PBServerFactory starting on 8800
+[-] Starting factory &lt;twisted.spread.pb.PBServerFactory instance at
+0x40604b4c&gt;
+[Broker,0,127.0.0.1] got pond: &lt;copy2_classes.ReceiverPond instance at
+0x406eb2ac&gt;
+[Broker,0,127.0.0.1] count 7
+</pre>
+
+<pre class="shell" xml:space="preserve">
+$ ./copy2_sender.py
+count 7
+pond arrived safe and sound
+Main loop terminated.
+</pre>
+
+
+
+<h3>Things To Watch Out For<a name="auto7"/></h3>
+
+<ul>
+
+ <li>The first argument to <code>setUnjellyableForClass</code> must refer
+ to the class <em>as known by the sender</em>. The sender has no way of
+ knowing about how your local <code>import</code> statements are set up,
+ and Python's flexible namespace semantics allow you to access the same
+ class through a variety of different names. You must match whatever the
+ sender does. Having both ends import the class from a separate file, using
+ a canonical module name (no <q>sibiling imports</q>), is a good way to get
+ this right, especially when both the sending and the receiving classes are
+ defined together, with the <code>setUnjellyableForClass</code> immediately
+ following them.</li>
+
+ <li>The class that is sent must inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>. The class that is registered to
+ receive it must inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code><a href="#footnote-2" title="pb.RemoteCopy is actually defined in twisted.spread.flavors, but pb.RemoteCopy is the preferred way to access it"><super>2</super></a>. </li>
+
+ <li>The same class can be used to send and receive. Just have it inherit
+ from both <code>pb.Copyable</code> and <code>pb.RemoteCopy</code>. This
+ will also make it possible to send the same class symmetrically back and
+ forth over the wire. But don't get confused about when it is coming (and
+ using <code>setCopyableState</code>) versus when it is going (using
+ <code>getStateToCopy</code>).</li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.InsecureJelly.html" title="twisted.spread.jelly.InsecureJelly">InsecureJelly</a></code>
+ exceptions are raised by the receiving end. They will be delivered
+ asynchronously to an <code>errback</code> handler. If you do not add one
+ to the <code>Deferred</code> returned by <code>callRemote</code>, then you
+ will never receive notification of the problem. </li>
+
+ <li>The class that is derived from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code> will be created using a
+ constructor <code>__init__</code> method that takes no arguments. All
+ setup must be performed in the <code>setCopyableState</code> method. As
+ the docstring on <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">RemoteCopy</a></code> says, don't implement a
+ constructor that requires arguments in a subclass of
+ <code>RemoteCopy</code>.</li>
+
+
+
+
+
+</ul>
+
+<h3>More Information<a name="auto8"/></h3>
+
+<ul>
+
+ <li> <code>pb.Copyable</code> is mostly implemented
+ in <code>twisted.spread.flavors</code>, and the docstrings there are
+ the best source of additional information.</li>
+
+ <li><code>Copyable</code> is also used in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.distrib.html" title="twisted.web.distrib">twisted.web.distrib</a></code> to deliver HTTP requests to other
+ programs for rendering, allowing subtrees of URL space to be delegated to
+ multiple programs (on multiple machines).</li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.manhole.explorer.html" title="twisted.manhole.explorer">twisted.manhole.explorer</a></code> also uses
+ <code>Copyable</code> to distribute debugging information from the program
+ under test to the debugging tool.</li>
+
+</ul>
+
+
+<h2>pb.Cacheable<a name="auto9"/></h2>
+
+<p>Sometimes the object you want to send to the remote process is big and
+slow. <q>big</q> means it takes a lot of data (storage, network bandwidth,
+processing) to represent its state. <q>slow</q> means that state doesn't
+change very frequently. It may be more efficient to send the full state only
+once, the first time it is needed, then afterwards only send the differences
+or changes in state whenever it is modified. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">pb.Cacheable</a></code> class provides a framework to
+implement this.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">pb.Cacheable</a></code> is derived
+from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>, so it is
+based upon the idea of an object's state being captured on the sending side,
+and then turned into a new object on the receiving side. This is extended to
+have an object <q>publishing</q> on the sending side (derived from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">pb.Cacheable</a></code>), matched with one
+<q>observing</q> on the receiving side (derived from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCache.html" title="twisted.spread.pb.RemoteCache">pb.RemoteCache</a></code>).</p>
+
+<p>To effectively use <code>pb.Cacheable</code>, you need to isolate changes
+to your object into accessor functions (specifically <q>setter</q>
+functions). Your object needs to get control <em>every</em> single time some
+attribute is changed<a href="#footnote-3" title="Of course you could be clever and add a hook to __setattr__, along with magical change-announcing subclasses of the usual builtin types, to detect changes that result from normal = set operations. The semi-magical property attributes that were introduced in Python 2.2 could be useful too. The result might be hard to maintain or extend, though."><super>3</super></a>.</p>
+
+<p>You derive your sender-side class from <code>pb.Cacheable</code>, and you
+add two methods: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.Cacheable.getStateToCacheAndObserveFor.html" title="twisted.spread.flavors.Cacheable.getStateToCacheAndObserveFor">getStateToCacheAndObserveFor</a></code>
+and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.Cacheable.stoppedObserving.html" title="twisted.spread.flavors.Cacheable.stoppedObserving">stoppedObserving</a></code>. The first
+is called when a remote caching reference is first created, and retrieves
+the data with which the cache is first filled. It also provides an
+object called the <q>observer</q> <a href="#footnote-4" title="This is actually a RemoteCacheObserver, but it isn't very useful to subclass or modify, so simply treat it as a little demon that sits in your pb.Cacheable class and helps you distribute change notifications. The only useful thing to do with it is to run its callRemote method, which acts just like a normal pb.Referenceable's method of the same name."><super>4</super></a> that points at that receiver-side cache. Every time the state of the object
+is changed, you give a message to the observer, informing them of the
+change. The other method, <code>stoppedObserving</code>, is called when the
+remote cache goes away, so that you can stop sending updates.</p>
+
+<p>On the receiver end, you make your cache class inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCache.html" title="twisted.spread.pb.RemoteCache">pb.RemoteCache</a></code>, and implement the
+ <code>setCopyableState</code> as you would for a <code>pb.RemoteCopy</code>
+object. In addition, you must implement methods to receive the updates sent
+to the observer by the <code>pb.Cacheable</code>: these methods should have
+names that start with <code>observe_</code>, and match the
+ <code>callRemote</code> invocations from the sender side just as the usual
+ <code>remote_*</code> and <code>perspective_*</code> methods match normal
+ <code>callRemote</code> calls. </p>
+
+<p>The first time a reference to the <code>pb.Cacheable</code> object is
+sent to any particular recipient, a sender-side Observer will be created for
+it, and the <code>getStateToCacheAndObserveFor</code> method will be called
+to get the current state and register the Observer. The state which that
+returns is sent to the remote end and turned into a local representation
+using <code>setCopyableState</code> just like <code>pb.RemoteCopy</code>,
+described above (in fact it inherits from that class). </p>
+
+<p>After that, your <q>setter</q> functions on the sender side should call
+ <code>callRemote</code> on the Observer, which causes <code>observe_*</code>
+methods to run on the receiver, which are then supposed to update the
+receiver-local (cached) state.</p>
+
+<p>When the receiver stops following the cached object and the last
+reference goes away, the <code>pb.RemoteCache</code> object can be freed.
+Just before it dies, it tells the sender side it no longer cares about the
+original object. When <em>that</em> reference count goes to zero, the
+Observer goes away and the <code>pb.Cacheable</code> object can stop
+announcing every change that takes place. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.Cacheable.stoppedObserving.html" title="twisted.spread.flavors.Cacheable.stoppedObserving">stoppedObserving</a></code> method is
+used to tell the <code>pb.Cacheable</code> that the Observer has gone
+away.</p>
+
+<p>With the <code>pb.Cacheable</code> and <code>pb.RemoteCache</code>
+classes in place, bound together by a call to
+ <code>pb.setUnjellyableForClass</code>, all that remains is to pass a
+reference to your <code>pb.Cacheable</code> over the wire to the remote end.
+The corresponding <code>pb.RemoteCache</code> object will automatically be
+created, and the matching methods will be used to keep the receiver-side
+slave object in sync with the sender-side master object.</p>
+
+<h3>Example<a name="auto10"/></h3>
+
+<p>Here is a complete example, in which the <code>MasterDuckPond</code> is
+controlled by the sending side, and the <code>SlaveDuckPond</code> is a
+cache that tracks changes to the master:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MasterDuckPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Cacheable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">ducks</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span> = []
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span> = <span class="py-src-variable">ducks</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I have [%d] ducks&quot;</span> % <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">addDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">duck</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">duck</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">o</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>: <span class="py-src-variable">o</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'addDuck'</span>, <span class="py-src-variable">duck</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">removeDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">duck</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">duck</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">o</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>: <span class="py-src-variable">o</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'removeDuck'</span>, <span class="py-src-variable">duck</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getStateToCacheAndObserveFor</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">perspective</span>, <span class="py-src-parameter">observer</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">observer</span>)
+ <span class="py-src-comment"># you should ignore pb.Cacheable-specific state, like self.observers</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span> <span class="py-src-comment"># in this case, just a list of ducks</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stoppedObserving</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">perspective</span>, <span class="py-src-parameter">observer</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">observer</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SlaveDuckPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">RemoteCache</span>):
+ <span class="py-src-comment"># This is a cache of a remote MasterDuckPond</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getDucks</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setCopyableState</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">state</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; cache - sitting, er, setting ducks&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span> = <span class="py-src-variable">state</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">observe_addDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">newDuck</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; cache - addDuck&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">newDuck</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">observe_removeDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">deadDuck</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; cache - removeDuck&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">deadDuck</span>)
+
+<span class="py-src-variable">pb</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">MasterDuckPond</span>, <span class="py-src-variable">SlaveDuckPond</span>)
+</pre><div class="caption">Source listing - <a href="listings/pb/cache_classes.py"><span class="filename">listings/pb/cache_classes.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">cache_classes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">MasterDuckPond</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Sender</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase1</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">remote</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">remote</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;takePond&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase2</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase2</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">addDuck</span>(<span class="py-src-string">&quot;ugly duckling&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">1</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">phase3</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase3</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;checkDucks&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase4</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase4</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">dummy</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">removeDuck</span>(<span class="py-src-string">&quot;one duck&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;checkDucks&quot;</span>)
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;ignorePond&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase5</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase5</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">dummy</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;shutdown&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase6</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase6</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">dummy</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">master</span> = <span class="py-src-variable">MasterDuckPond</span>([<span class="py-src-string">&quot;one duck&quot;</span>, <span class="py-src-string">&quot;two duck&quot;</span>])
+ <span class="py-src-variable">master</span>.<span class="py-src-variable">count</span>()
+
+ <span class="py-src-variable">sender</span> = <span class="py-src-variable">Sender</span>(<span class="py-src-variable">master</span>)
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">sender</span>.<span class="py-src-variable">phase1</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/cache_sender.py"><span class="filename">listings/pb/cache_sender.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cache_classes</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Receiver</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takePond</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got pond:&quot;</span>, <span class="py-src-variable">pond</span> <span class="py-src-comment"># a DuckPondCache</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote_checkDucks</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_checkDucks</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;[%d] ducks: &quot;</span> % <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>(), <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">getDucks</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_ignorePond</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-comment"># stop watching the pond</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;dropping pond&quot;</span>
+ <span class="py-src-comment"># gc causes __del__ causes 'decache' msg causes stoppedObserving</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;copy_receiver&quot;</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Receiver</span>())).<span class="py-src-variable">setServiceParent</span>(
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/pb/cache_receiver.py"><span class="filename">listings/pb/cache_receiver.py</span></a></div></div>
+<p>When run, this example emits the following:</p>
+
+<pre class="shell" xml:space="preserve">
+$ twistd -n -y cache_receiver.py
+[-] twisted.spread.pb.PBServerFactory starting on 8800
+[-] Starting factory &lt;twisted.spread.pb.PBServerFactory instance at
+0x40615acc&gt;
+[Broker,0,127.0.0.1] cache - sitting, er, setting ducks
+[Broker,0,127.0.0.1] got pond: &lt;cache_classes.SlaveDuckPond instance at
+0x406eb5ec&gt;
+[Broker,0,127.0.0.1] [2] ducks: ['one duck', 'two duck']
+[Broker,0,127.0.0.1] cache - addDuck
+[Broker,0,127.0.0.1] [3] ducks: ['one duck', 'two duck', 'ugly duckling']
+[Broker,0,127.0.0.1] cache - removeDuck
+[Broker,0,127.0.0.1] [2] ducks: ['two duck', 'ugly duckling']
+[Broker,0,127.0.0.1] dropping pond
+</pre>
+
+<pre class="shell" xml:space="preserve">
+$ ./cache_sender.py
+I have [2] ducks
+I have [3] ducks
+I have [2] ducks
+Main loop terminated.
+</pre>
+
+
+<p>Points to notice:</p>
+
+<ul>
+ <li>There is one <code>Observer</code> for each remote program that holds
+ an active reference. Multiple references inside the same program don't
+ matter: the serialization layer notices the duplicates and does the
+ appropriate reference counting<a href="#footnote-5" title="This applies to multiple references through the same Broker. If you've managed to make multiple TCP connections to the same program, you deserve whatever you get."><super>5</super></a>.
+ </li>
+
+ <li>Multiple Observers need to be kept in a list, and all of them need to
+ be updated when something changes. By sending the initial state at the
+ same time as you add the observer to the list, in a single atomic action
+ that cannot be interrupted by a state change, you insure that you can send
+ the same status update to all the observers.</li>
+
+ <li>The <code>observer.callRemote</code> calls can still fail. If the
+ remote side has disconnected very recently and
+ <code>stoppedObserving</code> has not yet been called, you may get a
+ <code>DeadReferenceError</code>. It is a good idea to add an errback to
+ those <code>callRemote</code>s to throw away such an error. This is a
+ useful idiom:
+
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">observer</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'foo'</span>, <span class="py-src-variable">arg</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">f</span>: <span class="py-src-variable">None</span>)
+</pre>
+ </li>
+
+
+ <li><code>getStateToCacheAndObserverFor</code> must return some object
+ that represents the current state of the object. This may simply be the
+ object's <code>__dict__</code> attribute. It is a good idea to remove the
+ <code>pb.Cacheable</code>-specific members of it before sending it to the
+ remote end. The list of Observers, in particular, should be left out, to
+ avoid dizzying recursive Cacheable references. The mind boggles as to the
+ potential consequences of leaving in such an item.</li>
+
+ <li>A <code>perspective</code> argument is available to
+ <code>getStateToCacheAndObserveFor</code>, as well as
+ <code>stoppedObserving</code>. I think the purpose of this is to allow
+ viewer-specific changes to the way the cache is updated. If all remote
+ viewers are supposed to see the same data, it can be ignored.</li>
+
+</ul>
+
+
+
+
+<h3>More Information<a name="auto11"/></h3>
+
+<ul>
+ <li>The best source for information comes from the docstrings
+ in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.html" title="twisted.spread.flavors">twisted.spread.flavors</a></code>,
+ where <code>pb.Cacheable</code> is implemented.</li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.manhole.explorer.html" title="twisted.manhole.explorer">twisted.manhole.explorer</a></code> uses
+ <code>Cacheable</code>, and does some fairly interesting things with it.</li>
+
+ <li>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.publish.html" title="twisted.spread.publish">spread.publish</a></code> module also
+ uses <code>Cacheable</code>, and might be a source of further
+ information.</li>
+</ul>
+
+
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">Note that, in this context, <q>unjelly</q> is
+a verb with the opposite meaning of <q>jelly</q>. The verb <q>to jelly</q>
+means to serialize an object or data structure into a sequence of bytes (or
+other primitive transmittable/storable representation), while <q>to
+unjelly</q> means to unserialize the bytestream into a live object in the
+receiver's memory space. <q>Unjellyable</q> is a noun, (<em>not</em> an
+adjective), referring to the the class that serves as a destination or
+recipient of the unjellying process. <q>A is unjellyable into B</q> means
+that a serialized representation A (of some remote object) can be
+unserialized into a local object of type B. It is these objects <q>B</q>
+that are the <q>Unjellyable</q> second argument of the
+<code>setUnjellyableForClass</code> function.
+In particular, <q>unjellyable</q> does <em>not</em> mean <q>cannot be
+jellied</q>. <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.Unpersistable.html" title="twisted.spread.jelly.Unpersistable">Unpersistable</a></code> means <q>not
+persistable</q>, but <q>unjelly</q>, <q>unserialize</q>, and <q>unpickle</q>
+mean to reverse the operations of <q>jellying</q>, <q>serializing</q>, and
+<q>pickling</q>.</span></a></li><li><a name="footnote-2"><span class="footnote"><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code> is actually defined
+ in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.html" title="twisted.spread.flavors">twisted.spread.flavors</a></code>, but
+ <code>pb.RemoteCopy</code> is the preferred way to access it</span></a></li><li><a name="footnote-3"><span class="footnote">Of course you could be clever and
+add a hook to <code>__setattr__</code>, along with magical change-announcing
+subclasses of the usual builtin types, to detect changes that result from
+normal <q>=</q> set operations. The semi-magical <q>property attributes</q>
+that were introduced in Python 2.2 could be useful too. The result might be
+hard to maintain or extend, though.</span></a></li><li><a name="footnote-4"><span class="footnote">This is actually a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCacheObserver.html" title="twisted.spread.pb.RemoteCacheObserver">RemoteCacheObserver</a></code>, but it isn't very
+useful to subclass or modify, so simply treat it as a little demon that sits
+in your <code>pb.Cacheable</code> class and helps you distribute change
+notifications. The only useful thing to do with it is to run its
+<code>callRemote</code> method, which acts just like a normal
+<code>pb.Referenceable</code>'s method of the same name.</span></a></li><li><a name="footnote-5"><span class="footnote">This applies to
+ multiple references through the same <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Broker.html" title="twisted.spread.pb.Broker">Broker</a></code>. If you've managed to make multiple
+ TCP connections to the same program, you deserve whatever you get.</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/pb-cred.html b/doc/core/howto/pb-cred.html
new file mode 100644
index 0000000..1599806
--- /dev/null
+++ b/doc/core/howto/pb-cred.html
@@ -0,0 +1,1724 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Authentication with Perspective Broker</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Authentication with Perspective Broker</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Compartmentalizing Services</a></li><ul><li><a href="#auto2">Incorrect Arguments</a></li><li><a href="#auto3">Unforgeable References</a></li><li><a href="#auto4">Argument Typechecking</a></li><li><a href="#auto5">Objects as Capabilities</a></li></ul><li><a href="#auto6">Avatars and Perspectives</a></li><li><a href="#auto7">Perspective Examples</a></li><ul><li><a href="#auto8">One Client</a></li><li><a href="#auto9">Two Clients</a></li><li><a href="#auto10">How that example worked</a></li><li><a href="#auto11">Anonymous Clients</a></li></ul><li><a href="#auto12">Using Avatars</a></li><ul><li><a href="#auto13">Avatar Interfaces</a></li><li><a href="#auto14">Logging Out</a></li><li><a href="#auto15">Making Avatars</a></li><li><a href="#auto16">Connecting and Disconnecting</a></li><li><a href="#auto17">Viewable</a></li><li><a href="#auto18">Chat Server with Avatars</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Overview<a name="auto0"/></h2>
+
+<p>The examples shown in <a href="pb-usage.html" shape="rect">Using Perspective
+Broker</a> demonstrate how to do basic remote method calls, but provided no
+facilities for authentication. In this context, authentication is about who
+gets which remote references, and how to restrict access to the <q>right</q>
+set of people or programs.</p>
+
+<p>As soon as you have a program which offers services to multiple users,
+where those users should not be allowed to interfere with each other, you
+need to think about authentication. Many services use the idea of an
+<q>account</q>, and rely upon fact that each user has access to only one
+account. Twisted uses a system called <a href="cred.html" shape="rect">cred</a> to
+handle authentication issues, and Perspective Broker has code to make it
+easy to implement the most common use cases.</p>
+
+<h2>Compartmentalizing Services<a name="auto1"/></h2>
+
+<p>Imagine how you would write a chat server using PB. The first step might
+be a <code>ChatServer</code> object which had a bunch of
+ <code>pb.RemoteReference</code>s that point at user clients. Pretend that
+those clients offered a <code>remote_print</code> method which lets the
+server print a message on the user's console. In that case, the server might
+look something like this:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatServer</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span> = {} <span class="py-src-comment"># indexed by name</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {} <span class="py-src-comment"># indexed by name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>, <span class="py-src-parameter">groupname</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">groupname</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>] = []
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>].<span class="py-src-variable">append</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">username</span>])
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_sendMessage</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">from_username</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">group</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">group</span>:
+ <span class="py-src-comment"># send the message to all members of the group</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">group</span>:
+ <span class="py-src-variable">user</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;print&quot;</span>,
+ <span class="py-src-string">&quot;&lt;%s&gt; says: %s&quot;</span> % (<span class="py-src-variable">from_username</span>,
+ <span class="py-src-variable">message</span>))
+</pre>
+
+<p>For now, assume that all clients have somehow acquired a
+ <code>pb.RemoteReference</code> to this <code>ChatServer</code> object,
+perhaps using <code>pb.Root</code> and <code>getRootObject</code> as
+described in the <a href="pb-usage.html" shape="rect">previous chapter</a>. In this
+scheme, when a user sends a message to the group, their client runs
+something like the following:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">remotegroup</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;sendMessage&quot;</span>, <span class="py-src-string">&quot;alice&quot;</span>, <span class="py-src-string">&quot;Hi, my name is alice.&quot;</span>)
+</pre>
+
+
+<h3>Incorrect Arguments<a name="auto2"/></h3>
+
+<p>You've probably seen the first problem: users can trivially spoof each
+other. We depend upon the user to pass a correct value in their
+ <q>username</q> argument, and have no way to tell if they're lying or not.
+There is nothing to prevent Alice from modifying her client to do:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">remotegroup</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;sendMessage&quot;</span>, <span class="py-src-string">&quot;bob&quot;</span>, <span class="py-src-string">&quot;i like pork&quot;</span>)
+</pre>
+
+<p>much to the horror of Bob's vegetarian friends.<a href="#footnote-1" title="Apparently Alice is one of those weirdos who has nothing better to do than to try and impersonate Bob. She will lie to her chat client, send incorrect objects to remote methods, even rewrite her local client code entirely to accomplish this juvenile prank. Given this adversarial relationship, one must wonder why she and Bob seem to spend so much time together: their adventures are clearly documented by the cryptographic literature."><super>1</super></a></p>
+
+<p>(In general, learn to get suspicious if you see any argument of a
+remotely-invokable method described as <q>must be X</q>)</p>
+
+<p>The best way to fix this is to keep track of the user's name locally,
+rather than asking them to send it to the server with each message. The best
+place to keep state is in an object, so this suggests we need a per-user
+object. Rather than choosing an obvious name<a href="#footnote-2" title="The obvious name is clearly ServerSidePerUserObjectWhichNobodyElseHasAccessTo, but because Python makes everything else so easy to read, it only seems fair to make your audience work for something."><super>2</super></a>, let's call this the
+ <code>User</code> class.
+</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">User</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>, <span class="py-src-parameter">server</span>, <span class="py-src-parameter">clientref</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">username</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span> = <span class="py-src-variable">server</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">clientref</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span>.<span class="py-src-variable">joinGroup</span>(<span class="py-src-variable">groupname</span>, <span class="py-src-variable">self</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_sendMessage</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span>.<span class="py-src-variable">sendMessage</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">groupname</span>, <span class="py-src-variable">message</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;print&quot;</span>, <span class="py-src-variable">message</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatServer</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span> = {} <span class="py-src-comment"># indexed by name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">groupname</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>] = []
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>].<span class="py-src-variable">append</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">sendMessage</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">from_username</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">group</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">group</span>:
+ <span class="py-src-comment"># send the message to all members of the group</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">group</span>:
+ <span class="py-src-variable">user</span>.<span class="py-src-variable">send</span>(<span class="py-src-string">&quot;&lt;%s&gt; says: %s&quot;</span> % (<span class="py-src-variable">from_username</span>, <span class="py-src-variable">message</span>))
+</pre>
+
+<p>Again, assume that each remote client gets access to a single
+ <code>User</code> object, which is created with the proper username.</p>
+
+<p>Note how the <code>ChatServer</code> object has no remote access: it
+isn't even <code>pb.Referenceable</code> anymore. This means that all access
+to it must be mediated through other objects, with code that is under your
+control.</p>
+
+<p>As long as Alice only has access to her own <code>User</code> object, she
+can no longer spoof Bob. The only way for her to invoke
+ <code>ChatServer.sendMessage</code> is to call her <code>User</code>
+object's <code>remote_sendMessage</code> method, and that method uses its
+own state to provide the <code>from_username</code> argument. It doesn't
+give her any way to change that state.</p>
+
+<p>This restriction is important. The <code>User</code> object is able to
+maintain its own integrity because there is a wall between the object and
+the client: the client cannot inspect or modify internal state, like the
+ <code>.name</code> attribute. The only way through this wall is via remote
+method invocations, and the only control Alice has over those invocations is
+when they get invoked and what arguments they are given.</p>
+
+<div class="note"><strong>Note: </strong>
+<p>No object can maintain its integrity against local threats: by design,
+Python offers no mechanism for class instances to hide their attributes, and
+once an intruder has a copy of <code>self.__dict__</code>, they can do
+everything the original object was able to do.</p>
+</div>
+
+
+<h3>Unforgeable References<a name="auto3"/></h3>
+
+<p>Now suppose you wanted to implement group parameters, for example a mode
+in which nobody was allowed to talk about mattresses because some users were
+sensitive and calming them down after someone said <q>mattress</q> is a
+hassle that's best avoided altogether. Again, per-group state implies a
+per-group object. We'll go out on a limb and call this the
+ <code>Group</code> object:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">User</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>, <span class="py-src-parameter">server</span>, <span class="py-src-parameter">clientref</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">username</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span> = <span class="py-src-variable">server</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">clientref</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">allowMattress</span>=<span class="py-src-parameter">True</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span>.<span class="py-src-variable">joinGroup</span>(<span class="py-src-variable">groupname</span>, <span class="py-src-variable">self</span>, <span class="py-src-variable">allowMattress</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;print&quot;</span>, <span class="py-src-variable">message</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Group</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">allowMattress</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">groupname</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> = <span class="py-src-variable">allowMattress</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = []
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">from_user</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> <span class="py-src-keyword">and</span> <span class="py-src-string">&quot;mattress&quot;</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">message</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">ValueError</span>, <span class="py-src-string">&quot;Don't say that word&quot;</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>:
+ <span class="py-src-variable">user</span>.<span class="py-src-variable">send</span>(<span class="py-src-string">&quot;&lt;%s&gt; says: %s&quot;</span> % (<span class="py-src-variable">from_user</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">message</span>))
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">addUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatServer</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span> = {} <span class="py-src-comment"># indexed by name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">allowMattress</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">groupname</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>] = <span class="py-src-variable">Group</span>(<span class="py-src-variable">groupname</span>, <span class="py-src-variable">allowMattress</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>].<span class="py-src-variable">addUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>]
+</pre>
+
+
+<p>This example takes advantage of the fact that
+ <code>pb.Referenceable</code> objects sent over a wire can be returned to
+you, and they will be turned into references to the same object that you
+originally sent. The client cannot modify the object in any way: all they
+can do is point at it and invoke its <code>remote_*</code> methods. Thus,
+you can be sure that the <code>.name</code> attribute remains the same as
+you left it. In this case, the client code would look something like
+this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientThing</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_print</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">message</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">join</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remoteUser</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;joinGroup&quot;</span>, <span class="py-src-string">&quot;#twisted&quot;</span>,
+ <span class="py-src-variable">allowMattress</span>=<span class="py-src-variable">False</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">gotGroup</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">gotGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">group</span>):
+ <span class="py-src-variable">group</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;send&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">remoteUser</span>, <span class="py-src-string">&quot;hi everybody&quot;</span>)
+</pre>
+
+<p>The <code>User</code> object is sent from the server side, and is turned
+into a <code>pb.RemoteReference</code> when it arrives at the client. The
+client sends it back to <code>Group.remote_send</code>, and PB turns it back
+into a reference to the original <code>User</code> when it gets there.
+ <code>Group.remote_send</code> can then use its <code>.name</code> attribute
+as the sender of the message.</p>
+
+<div class="note"><strong>Note: </strong>
+
+<p>Third party references (there aren't any)</p>
+
+<p>This technique also relies upon the fact that the
+ <code>pb.Referenceable</code> reference can <em>only</em> come from someone
+who holds a corresponding <code>pb.RemoteReference</code>. The design of the
+serialization mechanism (implemented in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.html" title="twisted.spread.jelly">twisted.spread.jelly</a></code>: pb, jelly, spread.. get it? Look for
+<q>banana</q>, too. What other networking framework
+can claim API names based on sandwich ingredients?) makes it impossible for
+a client to obtain a reference that they weren't explicitly given.
+References passed over the wire are given id numbers and recorded in a
+per-connection dictionary. If you didn't give them the reference, the id
+number won't be in the dict, and no amount of guessing by a malicious client
+will give them anything else. The dict goes away when the connection is
+dropped, further limiting the scope of those references.</p>
+
+<p>Futhermore, it is not possible for Bob to send <em>his</em>
+ <code>User</code> reference to Alice (perhaps over some other PB channel
+just between the two of them). Outside the context of Bob's connection to
+the server, that reference is just a meaningless number. To prevent
+confusion, PB will tell you if you try to give it away: when you try to hand
+a <code>pb.RemoteReference</code> to a third party, you'll get an exception
+(implemented with an assert in pb.py:364 RemoteReference.jellyFor).</p>
+
+<p>This helps the security model somewhat: only the client you gave the
+reference to can cause any damage with it. Of course, the client might be a
+brainless zombie, simply doing anything some third party wants. When it's
+not proxying <code>callRemote</code> invocations, it's probably terrorizing
+the living and searching out human brains for sustenance. In short, if you
+don't trust them, don't give them that reference.</p>
+
+<p>And remember that everything you've ever given them over that connection
+can come back to you. If expect the client to invoke your method with some
+object A that you sent to them earlier, and instead they send you object B
+(that you also sent to them earlier), and you don't check it somehow, then
+you've just opened up a security hole (we'll see an example of this
+shortly). It may be better to keep such objects in a dictionary on the
+server side, and have the client send you an index string instead. Doing it
+that way makes it obvious that they can send you anything they want, and
+improves the chances that you'll remember to implement the right checks.
+(This is exactly what PB is doing underneath, with a per-connection
+dictionary of <code>Referenceable</code> objects, indexed by a number).</p>
+
+<p>And, of course, you have to make sure you don't accidentally hand out a
+reference to the wrong object.</p>
+
+</div>
+
+
+<p>But again, note the vulnerability. If Alice holds a
+ <code>RemoteReference</code> to <em>any</em> object on the server side that
+has a <code>.name</code> attribute, she can use that name as a spoofed
+<q>from</q> parameter. As a simple example, what if her client code looked
+like:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientThing</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">join</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remoteUser</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;joinGroup&quot;</span>, <span class="py-src-string">&quot;#twisted&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">gotGroup</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">gotGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">group</span>):
+ <span class="py-src-variable">group</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;send&quot;</span>, <span class="py-src-variable">from_user</span>=<span class="py-src-variable">group</span>, <span class="py-src-string">&quot;hi everybody&quot;</span>)
+</pre>
+
+<p>This would let her send a message that appeared to come from
+<q>#twisted</q> rather than <q>Alice</q>. If she joined a group that
+happened to be named <q>bob</q> (perhaps it is the <q>How To Be Bob</q>
+channel, populated by Alice and countless others, a place where they can
+share stories about their best impersonating-Bob moments), then she would be
+able to emit a message that looked like <q>&lt;bob&gt; says: hi there</q>,
+and she has accomplished her lifelong goal.</p>
+
+
+<h3>Argument Typechecking<a name="auto4"/></h3>
+
+<p>There are two techniques to close this hole. The first is to have your
+remotely-invokable methods do type-checking on their arguments: if
+ <code>Group.remote_send</code> asserted <code>isinstance(from_user,
+User)</code> then Alice couldn't use non-User objects to do her spoofing,
+and hopefully the rest of the system is designed well enough to prevent her
+from obtaining access to somebody else's User object.</p>
+
+
+<h3>Objects as Capabilities<a name="auto5"/></h3>
+
+<p>The second technique is to avoid having the client send you the objects
+altogether. If they don't send you anything, there is nothing to verify. In
+this case, you would have to have a per-user-per-group object, in which the
+ <code>remote_send</code> method would only take a single
+ <code>message</code> argument. The <code>UserGroup</code> object is created
+with references to the only <code>User</code> and <code>Group</code> objects
+that it will ever use, so no lookups are needed:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">UserGroup</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">group</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">group</span> = <span class="py-src-variable">group</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">group</span>.<span class="py-src-variable">send</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">message</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Group</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">allowMattress</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">groupname</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> = <span class="py-src-variable">allowMattress</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = []
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">from_user</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> <span class="py-src-keyword">and</span> <span class="py-src-string">&quot;mattress&quot;</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">message</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">ValueError</span>, <span class="py-src-string">&quot;Don't say that word&quot;</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>:
+ <span class="py-src-variable">user</span>.<span class="py-src-variable">send</span>(<span class="py-src-string">&quot;&lt;%s&gt; says: %s&quot;</span> % (<span class="py-src-variable">from_user</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">message</span>))
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">addUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">user</span>)
+</pre>
+
+<p>The only message-sending method Alice has left is
+ <code>UserGroup.remote_send</code>, and it only accepts a message: there are
+no remaining ways to influence the <q>from</q> name.</p>
+
+<p>In this model, each remotely-accessible object represents a very small
+set of capabilities. Security is achieved by only granting a minimal set of
+abilities to each remote user.</p>
+
+<p>PB provides a shortcut which makes this technique easier to use. The
+ <code>Viewable</code> class will be discussed <a href="#viewable" shape="rect">below</a>.</p>
+
+<h2>Avatars and Perspectives<a name="auto6"/></h2>
+
+<p>In Twisted's <a href="cred.html" shape="rect">cred</a> system, an <q>Avatar</q> is
+an object that lives on the <q>server</q> side (defined here as the side
+farthest from the human who is trying to get something done) which lets the
+remote user get something done. The avatar isn't really a particular class,
+it's more like a description of a role that some object plays, as in <q>the
+Foo object here is acting as the user's avatar for this particular
+service</q>. Generally, the remote user has some way of getting their avatar
+to run some code. The avatar object may enforce some security checks, and
+provide additional data, then call other methods which get things done.</p>
+
+<p>The two pieces in the cred puzzle (for any protocol, not just PB) are:
+<q>what serves as the Avatar?</q>, and <q>how does the user get access to
+it?</q>.</p>
+
+<p>For PB, the first question is easy. The Avatar is a remotely-accessible
+object which can run code: this is a perfect description of
+ <code>pb.Referenceable</code> and its subclasses. We shall defer the second
+question until the next section.</p>
+
+<p>In the example above, you can think of the <code>ChatServer</code> and
+ <code>Group</code> objects as a service. The <code>User</code> object is the
+user's server-side representative: everything the user is capable of doing
+is done by running one of its methods. Anything that the server wants to do
+to the user (change their group membership, change their name, delete their
+pet cat, whatever) is done by manipulating the <code>User</code> object.</p>
+
+<p>There are multiple User objects living in peace and harmony around the
+ChatServer. Each has a different point of view on the services provided by
+the ChatServer and the Groups: each may belong to different groups, some
+might have more permissions than others (like the ability to create groups).
+These different points of view are called <q>Perspectives</q>. This is the
+origin of the term <q>Perspective</q> in <q>Perspective Broker</q>: PB
+provides and controls (i.e. <q>brokers</q>) access to Perspectives.</p>
+
+<p>Once upon a time, these local-representative objects were actually called
+ <code>pb.Perspective</code>. But this has changed with the advent of the
+rewritten cred system, and now the more generic term for a local
+representative object is an Avatar. But you will still see reference to
+ <q>Perspective</q> in the code, the docs, and the module names<a href="#footnote-3" title="We could just go ahead and rename Perspective Broker to be Avatar Broker, but 1) that would cause massive compatibility problems, and 2) AB doesn't fit into the whole sandwich-themed naming scheme nearly as well as PB does. If we changed it to AB, we'd probably have to change Banana to be CD (CoderDecoder), and Jelly to be EF (EncapsulatorFragmentor). twisted.spread would then have to be renamed twisted.alphabetsoup, and then the whole food-pun thing would start all over again."><super>3</super></a>. Just remember
+that perspectives and avatars are basically the same thing. </p>
+
+<p>Despite all we've been <a href="cred.html" shape="rect">telling you</a> about how
+Avatars are more of a concept than an actual class, the base class from
+which you can create your server-side avatar-ish objects is, in fact, named
+ <code>pb.Avatar</code><a href="#footnote-4" title="The avatar-ish class is named pb.Avatar because pb.Perspective was already taken, by the (now obsolete) oldcred perspective-ish class. It is a pity, but it simply wasn't possible both replace pb.Perspective in-place and maintain a reasonable level of backwards-compatibility."><super>4</super></a>. These objects behave very much like
+ <code>pb.Referenceable</code>. The only difference is that instead of
+offering <q>remote_FOO</q> methods, they offer <q>perspective_FOO</q>
+methods.</p>
+
+<p>The other way in which <code>pb.Avatar</code> differs from
+ <code>pb.Referenceable</code> is that the avatar objects are designed to be
+the first thing retrieved by a cred-using remote client. Just as
+ <code>PBClientFactory.getRootObject</code> gives the client access to a
+ <code>pb.Root</code> object (which can then provide access to all kinds of
+other objects), <code>PBClientFactory.login</code> gives client access to a
+ <code>pb.Avatar</code> object (which can return other references). </p>
+
+<p>So, the first half of using cred in your PB application is to create an
+Avatar object which implements <code>perspective_</code> methods and is
+careful to do useful things for the remote user while remaining vigilant
+against being tricked with unexpected argument values. It must also be
+careful to never give access to objects that the user should not have access
+to, whether by returning them directly, returning objects which contain
+them, or returning objects which can be asked (remotely) to provide
+them.</p>
+
+<p>The second half is how the user gets a <code>pb.RemoteReference</code> to
+your Avatar. As explained <a href="cred.html" shape="rect">elsewhere</a>, Avatars are
+obtained from a Realm. The Realm doesn't deal with authentication at all
+(usernames, passwords, public keys, challenge-response systems, retinal
+scanners, real-time DNA sequencers, etc). It simply takes an <q>avatarID</q>
+(which is effectively a username) and returns an Avatar object. The Portal
+and its Checkers deal with authenticating the user: by the time they are
+done, the remote user has proved their right to access the avatarID that is
+given to the Realm, so the Realm can return a remotely-controllable object
+that has whatever powers you wish to grant to this particular user. </p>
+
+<p>For PB, the realm is expected to return a <code>pb.Avatar</code> (or
+anything which implements <code>pb.IPerspective</code>, really, but there's
+no reason to not return a <code>pb.Avatar</code> subclass). This object will
+be given to the client just like a <code>pb.Root</code> would be without
+cred, and the user can get access to other objects through it (if you let
+them).</p>
+
+<p>The basic idea is that there is a separate IPerspective-implementing
+object (i.e. the Avatar subclass) (i.e. the <q>perspective</q>) for each
+user, and <em>only</em> the authorized user gets a remote reference to that
+object. You can store whatever permissions or capabilities the user
+possesses in that object, and then use them when the user invokes a remote
+method. You give the user access to the perspective object instead of the
+objects that do the real work.</p>
+
+
+<h2>Perspective Examples<a name="auto7"/></h2>
+
+<p>Here is a brief example of using a pb.Avatar. Most of the support code
+is magic for now: we'll explain it later.</p>
+
+<h3>One Client<a name="auto8"/></h3>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">checkers</span>, <span class="py-src-variable">portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyPerspective</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_foo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I am&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>, <span class="py-src-string">&quot;perspective_foo(&quot;</span>,<span class="py-src-variable">arg</span>,<span class="py-src-string">&quot;) called on&quot;</span>, <span class="py-src-variable">self</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">MyPerspective</span>(<span class="py-src-variable">avatarId</span>), <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+
+<span class="py-src-variable">p</span> = <span class="py-src-variable">portal</span>.<span class="py-src-variable">Portal</span>(<span class="py-src-variable">MyRealm</span>())
+<span class="py-src-variable">p</span>.<span class="py-src-variable">registerChecker</span>(
+ <span class="py-src-variable">checkers</span>.<span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>(<span class="py-src-variable">user1</span>=<span class="py-src-string">&quot;pass1&quot;</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">p</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb5server.py"><span class="filename">listings/pb/pb5server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">credentials</span>.<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-string">&quot;user1&quot;</span>, <span class="py-src-string">&quot;pass1&quot;</span>))
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">perspective</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got perspective ref:&quot;</span>, <span class="py-src-variable">perspective</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to foo(12)&quot;</span>
+ <span class="py-src-variable">perspective</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-number">12</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb5client.py"><span class="filename">listings/pb/pb5client.py</span></a></div></div>
+
+<p>Ok, so that wasn't really very exciting. It doesn't accomplish much more
+than the first PB example, and used a lot more code to do it. Let's try it
+again with two users this time.</p>
+
+<div class="note"><strong>Note: </strong>
+
+<p>When the client runs <code>login</code> to request the Perspective,
+they can provide it with an optional <code>client</code> argument (which
+must be a <code>pb.Referenceable</code> object). If they do, then a
+reference to that object will be handed to the realm's
+ <code>requestAvatar</code> in the <code>mind</code> argument.</p>
+
+<p>The server-side Perspective can use it to invoke remote methods on
+something in the client, so that the client doesn't always have to drive the
+interaction. In a chat server, the client object would be the one to which
+<q>display text</q> messages were sent. In a board game server, this would
+provide a way to tell the clients that someone has made a move, so they can
+update their game boards.</p>
+
+</div>
+
+<h3>Two Clients<a name="auto9"/></h3>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">checkers</span>, <span class="py-src-variable">portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyPerspective</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_foo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I am&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>, <span class="py-src-string">&quot;perspective_foo(&quot;</span>,<span class="py-src-variable">arg</span>,<span class="py-src-string">&quot;) called on&quot;</span>, <span class="py-src-variable">self</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">MyPerspective</span>(<span class="py-src-variable">avatarId</span>), <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+
+<span class="py-src-variable">p</span> = <span class="py-src-variable">portal</span>.<span class="py-src-variable">Portal</span>(<span class="py-src-variable">MyRealm</span>())
+<span class="py-src-variable">c</span> = <span class="py-src-variable">checkers</span>.<span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>(<span class="py-src-variable">user1</span>=<span class="py-src-string">&quot;pass1&quot;</span>,
+ <span class="py-src-variable">user2</span>=<span class="py-src-string">&quot;pass2&quot;</span>)
+<span class="py-src-variable">p</span>.<span class="py-src-variable">registerChecker</span>(<span class="py-src-variable">c</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">p</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb6server.py"><span class="filename">listings/pb/pb6server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">credentials</span>.<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-string">&quot;user1&quot;</span>, <span class="py-src-string">&quot;pass1&quot;</span>))
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">perspective</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got perspective1 ref:&quot;</span>, <span class="py-src-variable">perspective</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to foo(13)&quot;</span>
+ <span class="py-src-variable">perspective</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-number">13</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb6client1.py"><span class="filename">listings/pb/pb6client1.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">credentials</span>.<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-string">&quot;user2&quot;</span>, <span class="py-src-string">&quot;pass2&quot;</span>))
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">perspective</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got perspective2 ref:&quot;</span>, <span class="py-src-variable">perspective</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to foo(14)&quot;</span>
+ <span class="py-src-variable">perspective</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-number">14</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb6client2.py"><span class="filename">listings/pb/pb6client2.py</span></a></div></div>
+
+<p>While pb6server.py is running, try starting pb6client1, then pb6client2.
+Compare the argument passed by the <code>.callRemote()</code> in each
+client. You can see how each client gets connected to a different
+Perspective.</p>
+
+
+<h3>How that example worked<a name="auto10"/></h3><a name="smallexample" shape="rect"/>
+
+<p>Let's walk through the previous example and see what was going on.</p>
+
+<p>First, we created a subclass called <code>MyPerspective</code> which is
+our server-side Avatar. It implements a <code>perspective_foo</code> method
+that is exposed to the remote client.</p>
+
+<p>Second, we created a realm (an object which implements
+ <code>IRealm</code>, and therefore implements <code>requestAvatar</code>).
+This realm manufactures <code>MyPerspective</code> objects. It makes as many
+as we want, and names each one with the avatarID (a username) that comes out
+of the checkers. This MyRealm object returns two other objects as well,
+which we will describe later.</p>
+
+<p>Third, we created a portal to hold this realm. The portal's job is to
+dispatch incoming clients to the credential checkers, and then to request
+Avatars for any which survive the authentication process.</p>
+
+<p>Fourth, we made a simple checker (an object which implements
+ <code>IChecker</code>) to hold valid user/password pairs. The checker
+gets registered with the portal, so it knows who to ask when new
+clients connect. We use a checker named
+ <code>InMemoryUsernamePasswordDatabaseDontUse</code>, which suggests
+that 1: all the username/password pairs are kept in memory instead of
+being saved to a database or something, and 2: you shouldn't use
+it. The admonition against using it is because there are better
+schemes: keeping everything in memory will not work when you have
+thousands or millions of users to keep track of, the passwords will be
+stored in the .tap file when the application shuts down (possibly a
+security risk), and finally it is a nuisance to add or remove users
+after the checker is constructed.</p>
+
+<p>Fifth, we create a <code>pb.PBServerFactory</code> to listen on a TCP
+port. This factory knows how to connect the remote client to the Portal, so
+incoming connections will be handed to the authentication process. Other
+protocols (non-PB) would do something similar: the factory that creates
+Protocol objects will give those objects access to the Portal so
+authentication can take place.</p>
+
+<p>On the client side, a <code>pb.PBClientFactory</code> is created (as <a href="pb-usage.html" shape="rect">before</a>) and attached to a TCP connection. When the
+connection completes, the factory will be asked to produce a Protocol, and
+it will create a PB object. Unlike the previous chapter, where we used
+ <code>.getRootObject</code>, here we use <code>factory.login</code> to
+ initiate the cred authentication process. We provide a
+ <code>credentials</code> object, which is the client-side agent for doing
+our half of the authentication process. This process may involve several
+messages: challenges, responses, encrypted passwords, secure hashes, etc. We
+give our credentials object everything it will need to respond correctly (in
+this case, a username and password, but you could write a credential that
+used public-key encryption or even fancier techniques).</p>
+
+<p><code>login</code> returns a Deferred which, when it fires, will return a
+ <code>pb.RemoteReference</code> to the remote avatar. We can then do
+ <code>callRemote</code> to invoke a <code>perspective_foo</code> method on
+that Avatar.</p>
+
+
+<h3>Anonymous Clients<a name="auto11"/></h3>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Implement the realm for and run on port 8800 a PB service which allows both
+anonymous and username/password based access.
+
+Successful username/password-based login requests given an instance of
+MyPerspective with a name which matches the username with which they
+authenticated. Success anonymous login requests are given an instance of
+MyPerspective with the name &quot;Anonymous&quot;.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">startLogging</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ANONYMOUS</span>, <span class="py-src-variable">AllowAnonymousAccess</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>, <span class="py-src-variable">Portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span>.<span class="py-src-variable">pb</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Avatar</span>, <span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">PBServerFactory</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyPerspective</span>(<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Trivial avatar exposing a single remote method for demonstrative
+ purposes. All successful login attempts in this example will result in
+ an avatar which is an instance of this class.
+
+ @type name: C{str}
+ @ivar name: The username which was used during login or C{&quot;Anonymous&quot;}
+ if the login was anonymous (a real service might want to avoid the
+ collision this introduces between anonoymous users and authenticated
+ users named &quot;Anonymous&quot;).
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_foo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Print a simple message which gives the argument this method was
+ called with and this avatar's name.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I am %s. perspective_foo(%s) called on %s.&quot;</span> % (
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">arg</span>, <span class="py-src-variable">self</span>)
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyRealm</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Trivial realm which supports anonymous and named users by creating
+ avatars which are instances of MyPerspective for either.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>(<span class="py-src-string">&quot;MyRealm only handles IPerspective&quot;</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarId</span> <span class="py-src-keyword">is</span> <span class="py-src-variable">ANONYMOUS</span>:
+ <span class="py-src-variable">avatarId</span> = <span class="py-src-string">&quot;Anonymous&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">MyPerspective</span>(<span class="py-src-variable">avatarId</span>), <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">None</span>
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Create a PB server using MyRealm and run it on port 8800.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">startLogging</span>(<span class="py-src-variable">stdout</span>)
+
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">MyRealm</span>())
+
+ <span class="py-src-comment"># Here the username/password checker is registered.</span>
+ <span class="py-src-variable">c1</span> = <span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>(<span class="py-src-variable">user1</span>=<span class="py-src-string">&quot;pass1&quot;</span>, <span class="py-src-variable">user2</span>=<span class="py-src-string">&quot;pass2&quot;</span>)
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">registerChecker</span>(<span class="py-src-variable">c1</span>)
+
+ <span class="py-src-comment"># Here the anonymous checker is registered.</span>
+ <span class="py-src-variable">c2</span> = <span class="py-src-variable">AllowAnonymousAccess</span>()
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">registerChecker</span>(<span class="py-src-variable">c2</span>)
+
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">p</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pbAnonServer.py"><span class="filename">listings/pb/pbAnonServer.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Client which will talk to the server run by pbAnonServer.py, logging in
+either anonymously or with username/password credentials.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">err</span>, <span class="py-src-variable">startLogging</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">credentials</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Anonymous</span>, <span class="py-src-variable">UsernamePassword</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">gatherResults</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span>.<span class="py-src-variable">pb</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">PBClientFactory</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">error</span>(<span class="py-src-parameter">why</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Catch-all errback which simply logs the failure. This isn't expected to
+ be invoked in the normal case for this example.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">err</span>(<span class="py-src-variable">why</span>, <span class="py-src-variable">msg</span>)
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">perspective</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Login callback which invokes the remote &quot;foo&quot; method on the perspective
+ which the server returned.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got perspective1 ref:&quot;</span>, <span class="py-src-variable">perspective</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to foo(13)&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">perspective</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-number">13</span>)
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">finished</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Callback invoked when both logins and method calls have finished to shut
+ down the reactor so the example exits.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Connect to a PB server running on port 8800 on localhost and log in to
+ it, both anonymously and using a username/password it will recognize.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">startLogging</span>(<span class="py-src-variable">stdout</span>)
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+
+ <span class="py-src-variable">anonymousLogin</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">Anonymous</span>())
+ <span class="py-src-variable">anonymousLogin</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">anonymousLogin</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">error</span>, <span class="py-src-string">&quot;Anonymous login failed&quot;</span>)
+
+ <span class="py-src-variable">usernameLogin</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-string">&quot;user1&quot;</span>, <span class="py-src-string">&quot;pass1&quot;</span>))
+ <span class="py-src-variable">usernameLogin</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">usernameLogin</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">error</span>, <span class="py-src-string">&quot;Username/password login failed&quot;</span>)
+
+ <span class="py-src-variable">bothDeferreds</span> = <span class="py-src-variable">gatherResults</span>([<span class="py-src-variable">anonymousLogin</span>, <span class="py-src-variable">usernameLogin</span>])
+ <span class="py-src-variable">bothDeferreds</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">finished</span>)
+
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pbAnonClient.py"><span class="filename">listings/pb/pbAnonClient.py</span></a></div></div>
+
+<p>pbAnonServer.py implements a server based on pb6server.py, extending it to
+permit anonymous logins in addition to authenticated logins. An
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.checkers.AllowAnonymousAccess.html" title="twisted.cred.checkers.AllowAnonymousAccess">AllowAnonymousAccess</a></code>
+checker and an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.checkers.InMemoryUsernamePasswordDatabaseDontUse.html" title="twisted.cred.checkers.InMemoryUsernamePasswordDatabaseDontUse">InMemoryUsernamePasswordDatabaseDontUse</a></code>
+checker are registered and the
+client's choice of credentials object determines which is used to authenticate
+the login. In either case, the realm will be called on to create an avatar for
+the login. <code>AllowAnonymousAccess</code> always produces an <code>avatarId
+ </code> of <code>twisted.cred.checkers.ANONYMOUS</code>.</p>
+
+<p>On the client side, the only change is the use of an instance of
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.credentials.Anonymous.html" title="twisted.cred.credentials.Anonymous">Anonymous</a></code> when calling
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.PBClientFactory.login.html" title="twisted.spread.pb.PBClientFactory.login">PBClientFactory.login</a></code>.</p>
+
+
+<h2>Using Avatars<a name="auto12"/></h2>
+
+
+<h3>Avatar Interfaces<a name="auto13"/></h3>
+
+<p>The first element of the 3-tuple returned by <code>requestAvatar</code>
+indicates which Interface this Avatar implements. For PB avatars, it will
+always be <code>pb.IPerspective</code>, because that's the only interface
+these avatars implement.</p>
+
+<p>This element is present because <code>requestAvatar</code> is actually
+presented with a list of possible Interfaces. The question being posed to
+the Realm is: <q>do you have an avatar for (avatarID) that can implement one
+of the following set of Interfaces?</q>. Some portals and checkers might
+give a list of Interfaces and the Realm could pick; the PB code only knows
+how to do one, so we cannot take advantage of this feature.</p>
+
+<h3>Logging Out<a name="auto14"/></h3>
+
+<p>The third element of the 3-tuple is a zero-argument callable, which will
+be invoked by the protocol when the connection has been lost. We can use
+this to notify the Avatar when the client has lost its connection. This will
+be described in more detail below.</p>
+
+<h3>Making Avatars<a name="auto15"/></h3>
+
+<p>In the example above, we create Avatars upon request, during
+ <code>requestAvatar</code>. Depending upon the service, these Avatars might
+already exist before the connection is received, and might outlive the
+connection. The Avatars might also accept multiple connections.</p>
+
+<p>Another possibility is that the Avatars might exist ahead of time, but in
+a different form (frozen in a pickle and/or saved in a database). In this
+case, <code>requestAvatar</code> may need to perform a database lookup and
+then do something with the result before it can provide an avatar. In this
+case, it would probably return a Deferred so it could provide the real
+Avatar later, once the lookup had completed.</p>
+
+<p>Here are some possible implementations of
+ <code>MyRealm.requestAvatar</code>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+</p><span class="py-src-comment"># pre-existing, static avatars</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-variable">avatar</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarID</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">avatar</span>, <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+
+ <span class="py-src-comment"># database lookup and unpickling</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">database</span>.<span class="py-src-variable">fetchAvatar</span>(<span class="py-src-variable">avatarID</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">doUnpickle</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">d</span>, <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">doUnpickle</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pickled</span>):
+ <span class="py-src-variable">avatar</span> = <span class="py-src-variable">pickle</span>.<span class="py-src-variable">loads</span>(<span class="py-src-variable">pickled</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">avatar</span>
+
+ <span class="py-src-comment"># everybody shares the same Avatar</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">theOneAvatar</span>, <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+
+ <span class="py-src-comment"># anonymous users share one Avatar, named users each get their own</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarID</span> == <span class="py-src-variable">checkers</span>.<span class="py-src-variable">ANONYMOUS</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">anonAvatar</span>, <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarID</span>], <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+
+ <span class="py-src-comment"># anonymous users get independent (but temporary) Avatars</span>
+ <span class="py-src-comment"># named users get their own persistent one</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarID</span> == <span class="py-src-variable">checkers</span>.<span class="py-src-variable">ANONYMOUS</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">MyAvatar</span>(), <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarID</span>], <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+</pre>
+
+<p>The last example, note that the new <code>MyAvatar</code> instance is not
+saved anywhere: it will vanish when the connection is dropped. By contrast,
+the avatars that live in the <code>self.avatars</code> dictionary will
+probably get persisted into the .tap file along with the Realm, the Portal,
+and anything else that is referenced by the top-level Application object.
+This is an easy way to manage saved user profiles.</p>
+
+
+<h3>Connecting and Disconnecting<a name="auto16"/></h3>
+
+<p>It may be useful for your Avatars to be told when remote clients gain
+(and lose) access to them. For example, and Avatar might be updated by
+something in the server, and if there are clients attached, it should update
+them (through the <q>mind</q> argument which lets the Avatar do callRemote
+on the client).</p>
+
+<p>One common idiom which accomplishes this is to have the Realm tell the
+avatar that a remote client has just attached. The Realm can also ask the
+protocol to let it know when the connection goes away, so it can then inform
+the Avatar that the client has detached. The third member of the
+ <code>requestAvatar</code> return tuple is a callable which will be invoked
+when the connection is lost.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">MyPerspective</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clients</span> = []
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">attached</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">mind</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clients</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">mind</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;attached to&quot;</span>, <span class="py-src-variable">mind</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">detached</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">mind</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clients</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">mind</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;detached from&quot;</span>, <span class="py-src-variable">mind</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">update</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">c</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">clients</span>:
+ <span class="py-src-variable">c</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;update&quot;</span>, <span class="py-src-variable">message</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyRealm</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-variable">avatar</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarID</span>]
+ <span class="py-src-variable">avatar</span>.<span class="py-src-variable">attached</span>(<span class="py-src-variable">mind</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">avatar</span>, <span class="py-src-keyword">lambda</span> <span class="py-src-variable">a</span>=<span class="py-src-variable">avatar</span>:<span class="py-src-variable">a</span>.<span class="py-src-variable">detached</span>(<span class="py-src-variable">mind</span>)
+</pre>
+
+
+<h3>Viewable<a name="auto17"/></h3> <a name="viewable" shape="rect"/>
+
+<p>Once you have <code>IPerspective</code> objects (i.e. the Avatar) to
+represent users, the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Viewable.html" title="twisted.spread.pb.Viewable">Viewable</a></code> class can come into play. This
+class behaves a lot like <code>Referenceable</code>: it turns into a
+ <code>RemoteReference</code> when sent over the wire, and certain methods
+can be invoked by the holder of that reference. However, the methods that
+can be called have names that start with <code>view_</code> instead of <code>remote_</code>, and those methods are always called with an extra
+<code>perspective</code> argument that points to the Avatar through which
+the reference was sent:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Foo</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Viewable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">view_doFoo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">perspective</span>, <span class="py-src-parameter">arg1</span>, <span class="py-src-parameter">arg2</span>):
+ <span class="py-src-keyword">pass</span>
+</pre>
+
+<p>This is useful if you want to let multiple clients share a reference to
+the same object. The <code>view_</code> methods can use the
+ <q>perspective</q> argument to figure out which client is calling them. This
+gives them a way to do additional permission checks, do per-user accounting,
+etc.</p>
+
+<p>This is the shortcut which makes per-user-per-group capability objects
+much easier to use. Instead of creating such per-(user,group) objects, you
+just have per-group objects which inherit from <code>pb.Viewable</code>, and
+give the user references to them. The local <code>pb.Avatar</code> object
+will automatically show up as the <q>perspective</q> argument in the
+ <code>view_*</code> method calls, give you a chance to involve the Avatar in
+the process.</p>
+
+
+<h3>Chat Server with Avatars<a name="auto18"/></h3>
+
+<p>Combining all the above techniques, here is an example chat server which
+uses a fixed set of identities (say, for the three members of your bridge
+club, who hang out in <q>#NeedAFourth</q> hoping that someone will discover
+your server, guess somebody's password, break in, join the group, and also
+be available for a game next saturday afternoon).</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">portal</span>, <span class="py-src-variable">checkers</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatServer</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span> = {} <span class="py-src-comment"># indexed by name</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">allowMattress</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">groupname</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>] = <span class="py-src-variable">Group</span>(<span class="py-src-variable">groupname</span>, <span class="py-src-variable">allowMattress</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>].<span class="py-src-variable">addUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>]
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-variable">avatar</span> = <span class="py-src-variable">User</span>(<span class="py-src-variable">avatarID</span>)
+ <span class="py-src-variable">avatar</span>.<span class="py-src-variable">server</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span>
+ <span class="py-src-variable">avatar</span>.<span class="py-src-variable">attached</span>(<span class="py-src-variable">mind</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">avatar</span>, <span class="py-src-keyword">lambda</span> <span class="py-src-variable">a</span>=<span class="py-src-variable">avatar</span>:<span class="py-src-variable">a</span>.<span class="py-src-variable">detached</span>(<span class="py-src-variable">mind</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">User</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">attached</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">mind</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">mind</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">detached</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">mind</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">allowMattress</span>=<span class="py-src-parameter">True</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span>.<span class="py-src-variable">joinGroup</span>(<span class="py-src-variable">groupname</span>, <span class="py-src-variable">self</span>, <span class="py-src-variable">allowMattress</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;print&quot;</span>, <span class="py-src-variable">message</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Group</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Viewable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">allowMattress</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">groupname</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> = <span class="py-src-variable">allowMattress</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = []
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">addUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">view_send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">from_user</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> <span class="py-src-keyword">and</span> <span class="py-src-string">&quot;mattress&quot;</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">message</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">ValueError</span>, <span class="py-src-string">&quot;Don't say that word&quot;</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>:
+ <span class="py-src-variable">user</span>.<span class="py-src-variable">send</span>(<span class="py-src-string">&quot;&lt;%s&gt; says: %s&quot;</span> % (<span class="py-src-variable">from_user</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">message</span>))
+
+<span class="py-src-variable">realm</span> = <span class="py-src-variable">ChatRealm</span>()
+<span class="py-src-variable">realm</span>.<span class="py-src-variable">server</span> = <span class="py-src-variable">ChatServer</span>()
+<span class="py-src-variable">checker</span> = <span class="py-src-variable">checkers</span>.<span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>()
+<span class="py-src-variable">checker</span>.<span class="py-src-variable">addUser</span>(<span class="py-src-string">&quot;alice&quot;</span>, <span class="py-src-string">&quot;1234&quot;</span>)
+<span class="py-src-variable">checker</span>.<span class="py-src-variable">addUser</span>(<span class="py-src-string">&quot;bob&quot;</span>, <span class="py-src-string">&quot;secret&quot;</span>)
+<span class="py-src-variable">checker</span>.<span class="py-src-variable">addUser</span>(<span class="py-src-string">&quot;carol&quot;</span>, <span class="py-src-string">&quot;fido&quot;</span>)
+<span class="py-src-variable">p</span> = <span class="py-src-variable">portal</span>.<span class="py-src-variable">Portal</span>(<span class="py-src-variable">realm</span>, [<span class="py-src-variable">checker</span>])
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">p</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/chatserver.py"><span class="filename">listings/pb/chatserver.py</span></a></div></div>
+
+<p>Notice that the client uses <code>perspective_joinGroup</code> to both
+join a group and retrieve a <code>RemoteReference</code> to the
+ <code>Group</code> object. However, the reference they get is actually to a
+special intermediate object called a <code>pb.ViewPoint</code>. When they do
+ <code>group.callRemote(&quot;send&quot;, &quot;message&quot;)</code>, their avatar is inserted
+into the argument list that <code>Group.view_send</code> actually sees. This
+lets the group get their username out of the Avatar without giving the
+client an opportunity to spoof someone else.</p>
+
+<p>The client side code that joins a group and sends a message would look
+like this:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Client</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_print</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">message</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connect</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">credentials</span>.<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-string">&quot;alice&quot;</span>, <span class="py-src-string">&quot;1234&quot;</span>),
+ <span class="py-src-variable">client</span>=<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">perspective</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;connected, joining group #NeedAFourth&quot;</span>
+ <span class="py-src-comment"># this perspective is a reference to our User object. Save a reference</span>
+ <span class="py-src-comment"># to it here, otherwise it will get garbage collected after this call,</span>
+ <span class="py-src-comment"># and the server will think we logged out.</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">perspective</span> = <span class="py-src-variable">perspective</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">perspective</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;joinGroup&quot;</span>, <span class="py-src-string">&quot;#NeedAFourth&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">gotGroup</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">gotGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">group</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;joined group, now sending a message to all members&quot;</span>
+ <span class="py-src-comment"># 'group' is a reference to the Group object (through a ViewPoint)</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">group</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;send&quot;</span>, <span class="py-src-string">&quot;You can call me Al.&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">shutdown</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">shutdown</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+
+<span class="py-src-variable">Client</span>().<span class="py-src-variable">connect</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/chatclient.py"><span class="filename">listings/pb/chatclient.py</span></a></div></div>
+
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">Apparently Alice is one of those weirdos who has nothing
+better to do than to try and impersonate Bob. She will lie to her chat
+client, send incorrect objects to remote methods, even rewrite her local
+client code entirely to accomplish this juvenile prank. Given this
+adversarial relationship, one must wonder why she and Bob seem to spend so
+much time together: their adventures are clearly documented by the
+cryptographic literature.</span></a></li><li><a name="footnote-2"><span class="footnote">The
+obvious name is clearly
+<code>ServerSidePerUserObjectWhichNobodyElseHasAccessTo</code>, but because
+Python makes everything else so easy to read, it only seems fair to make
+your audience work for <em>something</em>.</span></a></li><li><a name="footnote-3"><span class="footnote">We could just go ahead and rename Perspective Broker to be
+Avatar Broker, but 1) that would cause massive compatibility problems, and 2)
+<q>AB</q> doesn't fit into the whole sandwich-themed naming scheme nearly as
+well as <q>PB</q> does. If we changed it to AB, we'd probably have to change
+Banana to be CD (CoderDecoder), and Jelly to be EF (EncapsulatorFragmentor).
+twisted.spread would then have to be renamed twisted.alphabetsoup, and then
+the whole food-pun thing would start all over again.</span></a></li><li><a name="footnote-4"><span class="footnote">The avatar-ish class is named
+<code>pb.Avatar</code> because <code>pb.Perspective</code> was already
+taken, by the (now obsolete) oldcred perspective-ish class. It is a pity,
+but it simply wasn't possible both replace <code>pb.Perspective</code>
+in-place <em>and</em> maintain a reasonable level of
+backwards-compatibility.</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/pb-intro.html b/doc/core/howto/pb-intro.html
new file mode 100644
index 0000000..dead735
--- /dev/null
+++ b/doc/core/howto/pb-intro.html
@@ -0,0 +1,320 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Introduction to Perspective Broker</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Introduction to Perspective Broker</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Object Roadmap</a></li><ul><li><a href="#auto2">Subclassing and Implementing</a></li></ul><li><a href="#auto3">Things you can Call Remotely</a></li><li><a href="#auto4">Things you can Copy Remotely</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p>Suppose you find yourself in control of both ends of the wire: you
+have two programs that need to talk to each other, and you get to use any
+protocol you want. If you can think of your problem in terms of objects that
+need to make method calls on each other, then chances are good that you can
+use Twisted's Perspective Broker protocol rather than trying to shoehorn
+your needs into something like HTTP, or implementing yet another RPC
+mechanism<a href="#footnote-1" title="Most of Twisted is like this. Hell, most of Unix is like this: if you think it would be useful, someone else has probably thought that way in the past, and acted on it, and you can take advantage of the tool they created to solve the same problem you're facing now."><super>1</super></a>.</p>
+
+<p>The Perspective Broker system (abbreviated <q>PB</q>, spawning numerous
+sandwich-related puns) is based upon a few central concepts:</p>
+
+<ul>
+
+ <li><em>serialization</em>: taking fairly arbitrary objects and types,
+ turning them into a chunk of bytes, sending them over a wire, then
+ reconstituting them on the other end. By keeping careful track of object
+ ids, the serialized objects can contain references to other objects and
+ the remote copy will still be useful. </li>
+
+ <li><em>remote method calls</em>: doing something to a local object and
+ causing a method to get run on a distant one. The local object is called a
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code>, and you
+ <q>do something</q> by running its <code>.callRemote</code> method.
+ </li>
+
+</ul>
+
+<p>This document will contain several examples that will (hopefully) appear
+redundant and verbose once you've figured out what's going on. To begin
+with, much of the code will just be labelled <q>magic</q>: don't worry about how
+these parts work yet. It will be explained more fully later.</p>
+
+<h2>Object Roadmap<a name="auto1"/></h2>
+
+<p>To start with, here are the major classes, interfaces, and
+functions involved in PB, with links to the file where they are
+defined (all of which are under twisted/, of course). Don't worry
+about understanding what they all do yet: it's easier to figure them
+out through their interaction than explaining them one at a time.</p>
+
+<ul>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">Factory</a></code>
+ : <code>internet/protocol.py</code></li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.PBServerFactory.html" title="twisted.spread.pb.PBServerFactory">PBServerFactory</a></code>
+ : <code>spread/pb.py</code></li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Broker.html" title="twisted.spread.pb.Broker">Broker</a></code>
+ : <code>spread/pb.py</code></li>
+
+</ul>
+
+<p>Other classes that are involved at some point:</p>
+
+<ul>
+
+ <li> <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code>
+ : <code>spread/pb.py</code> </li>
+
+ <li> <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>
+ : <code>spread/pb.py</code>, actually defined as
+ <code>twisted.spread.flavors.Root</code>
+ in <code>spread/flavors.py</code> </li>
+
+ <li> <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>
+ : <code>spread/pb.py</code>, actually defined as
+ <code>twisted.spread.flavors.Referenceable</code>
+ in <code>spread/flavors.py</code> </li>
+
+</ul>
+
+<p>Classes and interfaces that get involved when you start to care
+about authorization and security:</p>
+
+<ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.Portal.html" title="twisted.cred.portal.Portal">Portal</a></code>
+ : <code>cred/portal.py</code></li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.IRealm.html" title="twisted.cred.portal.IRealm">IRealm</a></code>
+ : <code>cred/portal.py</code></li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.IPerspective.html" title="twisted.spread.pb.IPerspective">IPerspective</a></code>
+ : <code>spread/pb.py</code>, which you will usually be interacting
+ with via <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Avatar.html" title="twisted.spread.pb.Avatar">pb.Avatar</a></code> (a basic implementor of the interface).</li>
+</ul>
+
+<h3>Subclassing and Implementing<a name="auto2"/></h3>
+
+<p>Technically you can subclass anything you want, but technically you
+could also write a whole new framework, which would just waste a lot
+of time. Knowing which classes are useful to subclass or which
+interfaces to implement is one of the bits of knowledge that's crucial
+to using PB (and all of Twisted) successfully. Here are some hints to
+get started:</p>
+
+<ul>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>: you'll
+ subclass these to make remotely-referenceable objects (i.e., objects
+ which you can call methods on remotely) using PB. You don't need to
+ change any of the existing behavior, just inherit all of it and add
+ the remotely-accessible methods that you want to export.</li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Avatar.html" title="twisted.spread.pb.Avatar">pb.Avatar</a></code>: You'll
+ be subclassing this when you get into PB programming with
+ authorization. This is an implementor of IPerspective.</li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.checkers.ICredentialsChecker.html" title="twisted.cred.checkers.ICredentialsChecker">ICredentialsChecker</a></code>: Implement this if
+ you want to authenticate your users against some sort of data store:
+ i.e., an LDAP database, an RDBMS, etc. There are already a few
+ implementations of this for various back-ends in
+ twisted.cred.checkers.</li>
+
+</ul>
+
+
+
+<h2>Things you can Call Remotely<a name="auto3"/></h2>
+
+<p>At this writing, there are three <q>flavors</q> of objects that can
+be accessed remotely through <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code> objects. Each of these
+flavors has a rule for how the <code class="python">callRemote</code>
+message is transformed into a local method call on the server. In
+order to use one of these <q>flavors</q>, subclass them and name your
+published methods with the appropriate prefix.
+
+<ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.IPerspective.html" title="twisted.spread.pb.IPerspective">twisted.spread.pb.IPerspective</a></code> implementors
+
+ <p>This is the first interface we deal with. It is a <q>perspective</q>
+ onto your PB application. Perspectives are slightly special because
+ they are usually the first object that a given user can access in
+ your application (after they log on). A user should only receive a
+ reference to their <em>own</em> perspective. PB works hard to
+ verify, as best it can, that any method that can be called on a
+ perspective directly is being called on behalf of the user who is
+ represented by that perspective. (Services with unusual
+ requirements for <q>on behalf of</q>, such as simulations with the
+ ability to posess another player's avatar, are accomplished by
+ providing indirected access to another user's perspective.)
+
+ </p>
+
+ <p>Perspectives are not usually serialized as remote references, so
+ do not return an IPerspective-implementor directly. </p>
+
+ <p>The way most people will want to implement IPerspective is by
+ subclassing pb.Avatar. Remotely accessible methods on pb.Avatar
+ instances are named with the <code class="python">perspective_</code> prefix. </p>
+
+ </li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">twisted.spread.pb.Referenceable</a></code>
+
+ <p>Referenceable objects are the simplest kind of PB object. You can call
+ methods on them and return them from methods to provide access to other
+ objects' methods. </p>
+
+ <p>However, when a method is called on a Referenceable, it's not possible to
+ tell who called it.</p>
+
+ <p>Remotely accessible methods on Referenceables are named with the
+ <code class="python">remote_</code> prefix.</p>
+
+ </li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Viewable.html" title="twisted.spread.pb.Viewable">twisted.spread.pb.Viewable</a></code>
+
+ <p>Viewable objects are remotely referenceable objects which have the
+ additional requirement that it must be possible to tell who is calling them.
+ The argument list to a Viewable's remote methods is modified in order to
+ include the Perspective representing the calling user.</p>
+
+ <p>Remotely accessible methods on Viewables are named with the
+ <code class="python">view_</code> prefix.</p>
+
+ </li>
+
+</ul>
+
+</p>
+
+<h2>Things you can Copy Remotely<a name="auto4"/></h2>
+
+<p>In addition to returning objects that you can call remote methods on, you
+can return structured copies of local objects.</p>
+
+<p>There are 2 basic flavors that allow for copying objects remotely. Again,
+you can use these by subclassing them. In order to specify what state you want
+to have copied when these are serialized, you can either use the Python default
+ <code class="python">__getstate__</code> or specialized method calls for that
+flavor.</p>
+
+<p>
+<ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">twisted.spread.pb.Copyable</a></code>
+
+ <p>This is the simpler kind of object that can be copied. Every time this
+ object is returned from a method or passed as an argument, it is serialized
+ and unserialized.</p>
+
+ <p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">Copyable</a></code>
+ provides a method you can override, <code class="py-prototype">getStateToCopyFor(perspective)</code>, which
+ allows you to decide what an object will look like for the
+ perspective who is requesting it. The <code class="python">perspective</code> argument will be the perspective
+ which is either passing an argument or returning a result an
+ instance of your Copyable class. </p>
+
+ <p>For security reasons, in order to allow a particular Copyable class to
+ actually be copied, you must declare a <code class="python">RemoteCopy</code>
+ handler for
+ that Copyable subclass. The easiest way to do this is to declare both in the
+ same module, like so:
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">flavors</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Foo</span>(<span class="py-src-parameter">flavors</span>.<span class="py-src-parameter">Copyable</span>):
+ <span class="py-src-keyword">pass</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteFoo</span>(<span class="py-src-parameter">flavors</span>.<span class="py-src-parameter">RemoteCopy</span>):
+ <span class="py-src-keyword">pass</span>
+<span class="py-src-variable">flavors</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">Foo</span>, <span class="py-src-variable">RemoteFoo</span>)
+</pre>
+
+ In this case, each time a Foo is copied between peers, a RemoteFoo will be
+ instantiated and populated with the Foo's state. If you do not do this, PB
+ will complain that there have been security violations, and it may close the
+ connection.
+ </p>
+
+ </li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">twisted.spread.pb.Cacheable</a></code>
+
+ <p>Let me preface this with a warning: Cacheable may be hard to understand.
+ The motivation for it may be unclear if you don't have some experience with
+ real-world applications that use remote method calling of some kind. Once
+ you understand why you need it, what it does will likely seem simple and
+ obvious, but if you get confused by this, forget about it and come back
+ later. It's possible to use PB without understanding Cacheable at all.
+ </p>
+
+ <p>Cacheable is a flavor which is designed to be copied only when necessary,
+ and updated on the fly as changes are made to it. When passed as an argument
+ or a return value, if a Cacheable exists on the side of the connection it is
+ being copied to, it will be referred to by ID and not copied.</p>
+
+ <p>Cacheable is designed to minimize errors involved in replicating an object
+ between multiple servers, especially those related to having stale
+ information. In order to do this, Cacheable automatically registers
+ observers and queries state atomically, together. You can override the
+ method <code class="py-prototype">getStateToCacheAndObserveFor(self,
+ perspective, observer)</code> in order to specify how your observers will be
+ stored and updated.
+ </p>
+
+ <p>Similar to
+ <code class="python">getStateToCopyFor</code>,
+ <code class="python">getStateToCacheAndObserveFor</code> gets passed a
+ perspective. It also gets passed an
+ <code class="python">observer</code>, which is a remote reference to a
+ <q>secret</q> fourth referenceable flavor:
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCache.html" title="twisted.spread.pb.RemoteCache">RemoteCache</a></code>.</p>
+
+ <p>A <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCache.html" title="twisted.spread.pb.RemoteCache">RemoteCache</a></code> is simply
+ the object that represents your
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">Cacheable</a></code> on the other side
+ of the connection. It is registered using the same method as
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">RemoteCopy</a></code>, above.
+ RemoteCache is different, however, in that it will be referenced by its peer.
+ It acts as a Referenceable, where all methods prefixed with
+ <code class="python">observe_</code> will be callable remotely. It is
+ recommended that your object maintain a list (note: library support for this
+ is forthcoming!) of observers, and update them using
+ <code class="python">callRemote</code> when the Cacheable changes in a way
+ that should be noticeable to its clients. </p>
+
+ <p>Finally, when all references to a
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">Cacheable</a></code> from a given
+ perspective are lost,
+ <code class="py-prototype">stoppedObserving(perspective, observer)</code>
+ will be called on the
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">Cacheable</a></code>, with the same
+ perspective/observer pair that <code>getStateToCacheAndObserveFor</code> was
+ originally called with. Any cleanup remote calls can be made there, as well
+ as removing the observer object from any lists which it was previously in.
+ Any further calls to this observer object will be invalid.</p>
+ </li>
+</ul>
+</p>
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">Most of Twisted is like this. Hell, most of
+Unix is like this: if <em>you</em> think it would be useful, someone else has
+probably thought that way in the past, and acted on it, and you can take
+advantage of the tool they created to solve the same problem you're facing
+now.</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/pb-limits.html b/doc/core/howto/pb-limits.html
new file mode 100644
index 0000000..b92a38d
--- /dev/null
+++ b/doc/core/howto/pb-limits.html
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: PB Limits</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">PB Limits</h1>
+ <div class="toc"><ol><li><a href="#auto0">Banana Limits</a></li><li><a href="#auto1">Perspective Broker Limits</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <p>There are a number of limits you might encounter when using Perspective
+Broker. This document is an attempt to prepare you for as many of them as
+possible so you can avoid them or at least recognize them when you do run
+into them.</p>
+
+ <h2>Banana Limits<a name="auto0"/></h2>
+
+ <p>Perspective Broker is implemented in terms of a simpler, less
+functional protocol called Banana. Twisted's implementation of Banana
+imposes a limit on the length of any sequence-like data type. This applies
+directly to lists and strings and indirectly to dictionaries, instances and
+other types. The purpose of this limit is to put an upper bound on the
+amount of memory which will be allocated to handle a message received over
+the network. Without, a malicious peer could easily perform a denial of
+service attack resulting in exhaustion of the receiver's memory. The basic
+limit is 640 * 1024 bytes, defined by <code>twisted.spread.banana.SIZE_LIMIT</code>.
+It's possible to raise this limit by changing this value (but take care to
+change it on both sides of the connection).</p>
+
+ <p>Another limit imposed by Twisted's Banana implementation is a limit on
+the size of long integers. The purpose of this limit is the same as the
+ <code>SIZE_LIMIT</code>. By default, only integers between -2 ** 448 and 2
+** 448 (exclusive) can be transferred. This limit can be changed using
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.banana.setPrefixLimit.html" title="twisted.spread.banana.setPrefixLimit">twisted.spread.banana.setPrefixLimit</a></code>.</p>
+
+ <h2>Perspective Broker Limits<a name="auto1"/></h2>
+
+ <p>Perspective Broker imposes an additional limit on top of these lower
+level limits. The number of local objects for which remote references may
+exist at a single time over a single connection, by default, is limited to
+1024, defined by <code>twisted.spread.pb.MAX_BROKER_REFS</code>. This limit
+also exists to prevent memory exhaustion attacks.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/pb-usage.html b/doc/core/howto/pb-usage.html
new file mode 100644
index 0000000..fb8cb70
--- /dev/null
+++ b/doc/core/howto/pb-usage.html
@@ -0,0 +1,1156 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using Perspective Broker</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using Perspective Broker</h1>
+ <div class="toc"><ol><li><a href="#auto0">Basic Example</a></li><li><a href="#auto1">Complete Example</a></li><li><a href="#auto2">References can come back to you</a></li><li><a href="#auto3">References to client-side objects</a></li><li><a href="#auto4">Raising Remote Exceptions</a></li><li><a href="#auto5">Try/Except blocks and Failure.trap </a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Basic Example<a name="auto0"/></h2>
+
+<p>The first example to look at is a complete (although somewhat trivial)
+application. It uses <code>PBServerFactory()</code> on the server side, and
+ <code>PBClientFactory()</code> on the client side.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echoer</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">st</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'echoing:'</span>, <span class="py-src-variable">st</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">st</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8789</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Echoer</span>()))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="../examples/pbsimple.py"><span class="filename">../examples/pbsimple.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">util</span>
+
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8789</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">d</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">object</span>: <span class="py-src-variable">object</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;echo&quot;</span>, <span class="py-src-string">&quot;hello network&quot;</span>))
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">echo</span>: <span class="py-src-string">'server echoed: '</span>+<span class="py-src-variable">echo</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">reason</span>: <span class="py-src-string">'error: '</span>+<span class="py-src-variable">str</span>(<span class="py-src-variable">reason</span>.<span class="py-src-variable">value</span>))
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">util</span>.<span class="py-src-variable">println</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="../examples/pbsimpleclient.py"><span class="filename">../examples/pbsimpleclient.py</span></a></div></div>
+
+<p>First we look at the server. This defines an Echoer class (derived from
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>), with a method called
+ <code>remote_echo()</code>.
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code> objects (because of
+their inheritance of
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>, described
+later) can define methods with names of the form <code>remote_*</code>; a
+client which obtains a remote reference to that
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code> object will be able to
+invoke those methods.</p>
+
+<p>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>-ish object is
+given to a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.PBServerFactory.html" title="twisted.spread.pb.PBServerFactory">pb.PBServerFactory</a></code><code>()</code>. This is a
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">Factory</a></code> object like
+any other: the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">Protocol</a></code> objects it creates for new
+connections know how to speak the PB protocol. The object you give to
+ <code>pb.PBServerFactory()</code> becomes the <q>root object</q>, which
+simply makes it available for the client to retrieve. The client may only
+request references to the objects you want to provide it: this helps you
+implement your security model. Because it is so common to export just a
+single object (and because a <code>remote_*</code> method on that one can
+return a reference to any other object you might want to give out), the
+simplest example is one where the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.PBServerFactory.html" title="twisted.spread.pb.PBServerFactory">PBServerFactory</a></code> is given the root object, and
+the client retrieves it.</p>
+
+<p>The client side uses
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.PBClientFactory.html" title="twisted.spread.pb.PBClientFactory">pb.PBClientFactory</a></code> to make a
+connection to a given port. This is a two-step process involving opening
+a TCP connection to a given host and port and requesting the root object
+using <code>.getRootObject()</code>.</p>
+
+<p>Because <code>.getRootObject()</code> has to wait until a network
+connection has been made and exchange some data, it may take a while,
+so it returns a Deferred, to which the gotObject() callback is
+attached. (See the documentation on <a href="defer.html" shape="rect">Deferring
+Execution</a> for a complete explanation of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>s). If and when the
+connection succeeds and a reference to the remote root object is
+obtained, this callback is run. The first argument passed to the
+callback is a remote reference to the distant root object. (you can
+give other arguments to the callback too, see the other parameters for
+ <code>.addCallback()</code> and <code>.addCallbacks()</code>).</p>
+
+<p>The callback does:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">object</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;echo&quot;</span>, <span class="py-src-string">&quot;hello network&quot;</span>)
+</pre>
+
+<p>which causes the server's <code>.remote_echo()</code> method to be invoked.
+(running <code>.callRemote(&quot;boom&quot;)</code> would cause
+ <code>.remote_boom()</code> to be run, etc). Again because of the delay
+involved, <code>callRemote()</code> returns a
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>. Assuming the
+remote method was run without causing an exception (including an attempt to
+invoke an unknown method), the callback attached to that
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> will be
+invoked with any objects that were returned by the remote method call.</p>
+
+<p>In this example, the server's <code>Echoer</code> object has a method
+invoked, <em>exactly</em> as if some code on the server side had done:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">echoer_object</span>.<span class="py-src-variable">remote_echo</span>(<span class="py-src-string">&quot;hello network&quot;</span>)
+</pre>
+
+<p>and from the definition of <code>remote_echo()</code> we see that this just
+returns the same string it was given: <q>hello network</q>.</p>
+
+<p>From the client's point of view, the remote call gets another <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> object instead of
+that string. <code>callRemote()</code> <em>always</em> returns a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>. This is why PB is
+described as a system for <q>translucent</q> remote method calls instead of
+<q>transparent</q> ones: you cannot pretend that the remote object is really
+local. Trying to do so (as some other RPC mechanisms do, coughCORBAcough)
+breaks down when faced with the asynchronous nature of the network. Using
+Deferreds turns out to be a very clean way to deal with the whole thing.</p>
+
+<p>The remote reference object (the one given to
+ <code>getRootObject()</code>'s success callback) is an instance the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code> class. This means
+you can use it to invoke methods on the remote object that it refers to. Only
+instances of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code> are eligible for
+ <code>.callRemote()</code>. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code> object is the one that lives
+on the remote side (the client, in this case), not the local side (where the
+actual object is defined).</p>
+
+<p>In our example, the local object is that <code>Echoer()</code> instance,
+which inherits from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>,
+which inherits from
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>. It is that
+ <code>Referenceable</code> class that makes the object eligible to be available
+for remote method calls<a href="#footnote-1" title="There are a few other classes that can bestow this ability, but pb.Referenceable is the easiest to understand; see 'flavors' below for details on the others."><super>1</super></a>. If you have
+an object that is Referenceable, then any client that manages to get a
+reference to it can invoke any <code>remote_*</code> methods they please.</p>
+
+<div class="note"><strong>Note: </strong>
+<p>The <em>only</em> thing they can do is invoke those
+methods. In particular, they cannot access attributes. From a security point
+of view, you control what they can do by limiting what the
+<code>remote_*</code> methods can do.</p>
+
+<p>Also note: the other classes like
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">Referenceable</a></code> allow access to
+other methods, in particular <code>perspective_*</code> and <code>view_*</code>
+may be accessed. Don't write local-only methods with these names, because then
+remote callers will be able to do more than you intended.</p>
+
+<p>Also also note: the other classes like
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code> <em>do</em> allow
+access to attributes, but you control which ones they can see.</p>
+</div>
+
+<p>You don't have to be a
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code> to be remotely callable,
+but you do have to be
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>. (Objects that
+inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>
+but not from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code> can be
+remotely called, but only
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>-ish objects can be given
+to the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.PBServerFactory.html" title="twisted.spread.pb.PBServerFactory">PBServerFactory</a></code>.)</p>
+
+<h2>Complete Example<a name="auto1"/></h2>
+
+<p>Here is an example client and server which uses <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code> as a root object and as the
+result of a remotely exposed method. In each context, methods can be invoked
+on the exposed <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">Referenceable</a></code>
+instance. In this example, the initial root object has a method that returns a
+reference to the second object.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Two</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_three</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Two.three was given&quot;</span>, <span class="py-src-variable">arg</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getTwo</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">two</span> = <span class="py-src-variable">Two</span>()
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;returning a Two called&quot;</span>, <span class="py-src-variable">two</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">two</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">One</span>()))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb1server.py"><span class="filename">listings/pb/pb1server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">got_obj1</span>, <span class="py-src-variable">err_obj1</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">err_obj1</span>(<span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;error getting first object&quot;</span>, <span class="py-src-variable">reason</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj1</span>(<span class="py-src-parameter">obj1</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got first object:&quot;</span>, <span class="py-src-variable">obj1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to getTwo&quot;</span>
+ <span class="py-src-variable">def2</span> = <span class="py-src-variable">obj1</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;getTwo&quot;</span>)
+ <span class="py-src-variable">def2</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">got_obj2</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj2</span>(<span class="py-src-parameter">obj2</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got second object:&quot;</span>, <span class="py-src-variable">obj2</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;telling it to do three(12)&quot;</span>
+ <span class="py-src-variable">obj2</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;three&quot;</span>, <span class="py-src-number">12</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb1client.py"><span class="filename">listings/pb/pb1client.py</span></a></div></div>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.PBClientFactory.getRootObject.html" title="twisted.spread.pb.PBClientFactory.getRootObject">pb.PBClientFactory.getRootObject</a></code> will
+handle all the details of waiting for the creation of a connection.
+It returns a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>, which will have its
+callback called when the reactor connects to the remote server and
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.PBClientFactory.html" title="twisted.spread.pb.PBClientFactory">pb.PBClientFactory</a></code> gets the
+root, and have its <code class="python">errback</code> called when the
+object-connection fails for any reason, whether it was host lookup
+failure, connection refusal, or some server-side error.
+</p>
+
+<p>The root object has a method called <code>remote_getTwo</code>, which
+returns the <code>Two()</code> instance. On the client end, the callback gets
+a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code> to that
+instance. The client can then invoke two's <code>.remote_three()</code>
+method.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code>
+objects have one method which is their purpose for being: <code class="python">callRemote</code>. This method allows you to call a
+remote method on the object being referred to by the Reference. <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.callRemote.html" title="twisted.spread.pb.RemoteReference.callRemote">RemoteReference.callRemote</a></code>, like <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.PBClientFactory.getRootObject.html" title="twisted.spread.pb.PBClientFactory.getRootObject">pb.PBClientFactory.getRootObject</a></code>, returns
+a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>.
+When a response to the method-call being sent arrives, the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>'s <code class="python">callback</code> or <code class="python">errback</code>
+will be made, depending on whether an error occurred in processing the
+method call.</p>
+
+<p>You can use this technique to provide access to arbitrary sets of objects.
+Just remember that any object that might get passed <q>over the wire</q> must
+inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">Referenceable</a></code>
+(or one of the other flavors). If you try to pass a non-Referenceable object
+(say, by returning one from a <code>remote_*</code> method), you'll get an
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.InsecureJelly.html" title="twisted.spread.jelly.InsecureJelly">InsecureJelly</a></code>
+exception<a href="#footnote-2" title="This can be overridden, by subclassing one of the Serializable flavors and defining custom serialization code for your class. See Passing Complex Types for details."><super>2</super></a>.</p>
+
+
+<h2>References can come back to you<a name="auto2"/></h2>
+
+<p>If your server gives a reference to a client, and then that client gives
+the reference back to the server, the server will wind up with the same
+object it gave out originally. The serialization layer watches for returning
+reference identifiers and turns them into actual objects. You need to stay
+aware of where the object lives: if it is on your side, you do actual method
+calls. If it is on the other side, you do
+ <code>.callRemote()</code><a href="#footnote-3" title="The binary nature of this local vs. remote scheme works because you cannot give RemoteReferences to a third party. If you could, then your object A could go to B, B could give it to C, C might give it back to you, and you would be hard pressed to tell if the object lived in C's memory space, in B's, or if it was really your own object, tarnished and sullied after being handed down like a really ugly picture that your great aunt owned and which nobody wants but which nobody can bear to throw out. Ok, not really like that, but you get the idea."><super>3</super></a>.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Two</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_print</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;two.print was given&quot;</span>, <span class="py-src-variable">arg</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">two</span>):
+ <span class="py-src-comment">#pb.Root.__init__(self) # pb.Root doesn't implement __init__</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">two</span> = <span class="py-src-variable">two</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getTwo</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;One.getTwo(), returning my two called&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">two</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">two</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_checkTwo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">newtwo</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;One.checkTwo(): comparing my two&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">two</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;One.checkTwo(): against your two&quot;</span>, <span class="py-src-variable">newtwo</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">two</span> == <span class="py-src-variable">newtwo</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;One.checkTwo(): our twos are the same&quot;</span>
+
+
+<span class="py-src-variable">two</span> = <span class="py-src-variable">Two</span>()
+<span class="py-src-variable">root_obj</span> = <span class="py-src-variable">One</span>(<span class="py-src-variable">two</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">root_obj</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb2server.py"><span class="filename">listings/pb/pb2server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">foo</span> = <span class="py-src-variable">Foo</span>()
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>().<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">foo</span>.<span class="py-src-variable">step1</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-comment"># keeping globals around is starting to get ugly, so we use a simple class</span>
+<span class="py-src-comment"># instead. Instead of hooking one function to the next, we hook one method</span>
+<span class="py-src-comment"># to the next.</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Foo</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">oneRef</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">step1</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">obj</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got one object:&quot;</span>, <span class="py-src-variable">obj</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">oneRef</span> = <span class="py-src-variable">obj</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to getTwo&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">oneRef</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;getTwo&quot;</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">step2</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">step2</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">two</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got two object:&quot;</span>, <span class="py-src-variable">two</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;giving it back to one&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;one is&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">oneRef</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">oneRef</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;checkTwo&quot;</span>, <span class="py-src-variable">two</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb2client.py"><span class="filename">listings/pb/pb2client.py</span></a></div></div>
+
+<p>The server gives a <code>Two()</code> instance to the client, who then
+returns the reference back to the server. The server compares the <q>two</q>
+given with the <q>two</q> received and shows that they are the same, and that
+both are real objects instead of remote references.</p>
+
+<p>A few other techniques are demonstrated in <code>pb2client.py</code>. One
+is that the callbacks are are added with <code>.addCallback</code> instead
+of <code>.addCallbacks</code>. As you can tell from the <a href="defer.html" shape="rect">Deferred</a> documentation, <code>.addCallback</code> is a
+simplified form which only adds a success callback. The other is that to
+keep track of state from one callback to the next (the remote reference to
+the main One() object), we create a simple class, store the reference in an
+instance thereof, and point the callbacks at a sequence of bound methods.
+This is a convenient way to encapsulate a state machine. Each response kicks
+off the next method, and any data that needs to be carried from one state to
+the next can simply be saved as an attribute of the object.</p>
+
+<p>Remember that the client can give you back any remote reference you've
+given them. Don't base your zillion-dollar stock-trading clearinghouse
+server on the idea that you trust the client to give you back the right
+reference. The security model inherent in PB means that they can <em>only</em>
+give you back a reference that you've given them for the current connection
+(not one you've given to someone else instead, nor one you gave them last
+time before the TCP session went down, nor one you haven't yet given to the
+client), but just like with URLs and HTTP cookies, the particular reference
+they give you is entirely under their control.</p>
+
+
+<h2>References to client-side objects<a name="auto3"/></h2>
+
+<p>Anything that's Referenceable can get passed across the wire, <em>in
+either direction</em>. The <q>client</q> can give a reference to the
+ <q>server</q>, and then the server can use .callRemote() to invoke methods on
+the client end. This fuzzes the distinction between <q>client</q> and
+ <q>server</q>: the only real difference is who initiates the original TCP
+connection; after that it's all symmetric.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takeTwo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">two</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;received a Two called&quot;</span>, <span class="py-src-variable">two</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;telling it to print(12)&quot;</span>
+ <span class="py-src-variable">two</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;print&quot;</span>, <span class="py-src-number">12</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">One</span>()))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb3server.py"><span class="filename">listings/pb/pb3server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Two</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_print</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Two.print() called with&quot;</span>, <span class="py-src-variable">arg</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">two</span> = <span class="py-src-variable">Two</span>()
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">got_obj</span>, <span class="py-src-variable">two</span>) <span class="py-src-comment"># hands our 'two' to the callback</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">obj</span>, <span class="py-src-parameter">two</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got One:&quot;</span>, <span class="py-src-variable">obj</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;giving it our two&quot;</span>
+ <span class="py-src-variable">obj</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;takeTwo&quot;</span>, <span class="py-src-variable">two</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb3client.py"><span class="filename">listings/pb/pb3client.py</span></a></div></div>
+
+<p>In this example, the client gives a reference to its own object to the
+server. The server then invokes a remote method on the client-side
+object.</p>
+
+
+<h2>Raising Remote Exceptions<a name="auto4"/></h2>
+
+<p>Everything so far has covered what happens when things go right. What
+about when they go wrong? The Python Way is to raise an exception of some
+sort. The Twisted Way is the same.</p>
+
+<p>The only special thing you do is to define your <code>Exception</code>
+subclass by deriving it from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Error.html" title="twisted.spread.pb.Error">pb.Error</a></code>. When any remotely-invokable method
+(like <code>remote_*</code> or <code>perspective_*</code>) raises a
+ <code>pb.Error</code>-derived exception, a serialized form of that Exception
+object will be sent back over the wire<a href="#footnote-4" title="To be precise, the Failure will be sent if any exception is raised, not just pb.Error-derived ones. But the server will print ugly error messages if you raise ones that aren't derived from pb.Error."><super>4</super></a>. The other side (which
+did <code>callRemote</code>) will have the <q><code>errback</code></q>
+callback run with a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code> object that contains a copy of
+the exception object. This <code>Failure</code> object can be queried to
+retrieve the error message and a stack traceback.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code> is a
+special class, defined in <code>twisted/python/failure.py</code>, created to
+make it easier to handle asynchronous exceptions. Just as exception handlers
+can be nested, <code>errback</code> functions can be chained. If one errback
+can't handle the particular type of failure, it can be <q>passed along</q> to a
+errback handler further down the chain.</p>
+
+<p>For simple purposes, think of the <code>Failure</code> as just a container
+for remotely-thrown <code>Exception</code> objects. To extract the string that
+was put into the exception, use its <code>.getErrorMessage()</code> method.
+To get the type of the exception (as a string), look at its
+ <code>.type</code> attribute. The stack traceback is available too. The
+intent is to let the errback function get just as much information about the
+exception as Python's normal <code>try:</code> clauses do, even though the
+exception occurred in somebody else's memory space at some unknown time in
+the past.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyError</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Error</span>):
+ <span class="py-src-string">&quot;&quot;&quot;This is an Expected Exception. Something bad happened.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyError2</span>(<span class="py-src-parameter">Exception</span>):
+ <span class="py-src-string">&quot;&quot;&quot;This is an Unexpected Exception. Something really bad happened.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_broken</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">msg</span> = <span class="py-src-string">&quot;fall down go boom&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;raising a MyError exception with data '%s'&quot;</span> % <span class="py-src-variable">msg</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">MyError</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_broken2</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">msg</span> = <span class="py-src-string">&quot;hadda owie&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;raising a MyError2 exception with data '%s'&quot;</span> % <span class="py-src-variable">msg</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">MyError2</span>(<span class="py-src-variable">msg</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">One</span>()))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/exc_server.py"><span class="filename">listings/pb/exc_server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">got_obj</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">obj</span>):
+ <span class="py-src-comment"># change &quot;broken&quot; into &quot;broken2&quot; to demonstrate an unhandled exception</span>
+ <span class="py-src-variable">d2</span> = <span class="py-src-variable">obj</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;broken&quot;</span>)
+ <span class="py-src-variable">d2</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">working</span>)
+ <span class="py-src-variable">d2</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">broken</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">working</span>():
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;erm, it wasn't *supposed* to work..&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">broken</span>(<span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got remote Exception&quot;</span>
+ <span class="py-src-comment"># reason should be a Failure (or subclass) holding the MyError exception</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; .__class__ =&quot;</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">__class__</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; .getErrorMessage() =&quot;</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">getErrorMessage</span>()
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; .type =&quot;</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">type</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/exc_client.py"><span class="filename">listings/pb/exc_client.py</span></a></div></div>
+
+<pre class="shell" xml:space="preserve">
+$ ./exc_client.py
+got remote Exception
+ .__class__ = twisted.spread.pb.CopiedFailure
+ .getErrorMessage() = fall down go boom
+ .type = __main__.MyError
+Main loop terminated.
+</pre>
+
+<p>Oh, and what happens if you raise some other kind of exception? Something
+that <em>isn't</em> subclassed from <code>pb.Error</code>? Well, those are
+called <q>unexpected exceptions</q>, which make Twisted think that something
+has <em>really</em> gone wrong. These will raise an exception on the
+ <em>server</em> side. This won't break the connection (the exception is
+trapped, just like most exceptions that occur in response to network
+traffic), but it will print out an unsightly stack trace on the server's
+stderr with a message that says <q>Peer Will Receive PB Traceback</q>, just
+as if the exception had happened outside a remotely-invokable method. (This
+message will go the current log target, if <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.startLogging.html" title="twisted.python.log.startLogging">log.startLogging</a></code> was used to redirect it). The
+client will get the same <code>Failure</code> object in either case, but
+subclassing your exception from <code>pb.Error</code> is the way to tell
+Twisted that you expect this sort of exception, and that it is ok to just
+let the client handle it instead of also asking the server to complain. Look
+at <code>exc_client.py</code> and change it to invoke <code>broken2()</code>
+instead of <code>broken()</code> to see the change in the server's
+behavior.</p>
+
+<p>If you don't add an <code>errback</code> function to the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>, then a remote
+exception will still send a <code>Failure</code> object back over, but it
+will get lodged in the <code>Deferred</code> with nowhere to go. When that
+ <code>Deferred</code> finally goes out of scope, the side that did
+ <code>callRemote</code> will emit a message about an <q>Unhandled error in
+Deferred</q>, along with an ugly stack trace. It can't raise an exception at
+that point (after all, the <code>callRemote</code> that triggered the
+problem is long gone), but it will emit a traceback. So be a good programmer
+and <em>always</em> add <code>errback</code> handlers, even if they are just
+calls to <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.err.html" title="twisted.python.log.err">log.err</a></code>.</p>
+
+<h2>Try/Except blocks and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.trap.html" title="twisted.python.failure.Failure.trap">Failure.trap</a></code> <a name="auto5"/></h2>
+
+<p>To implement the equivalent of the Python try/except blocks (which can
+trap particular kinds of exceptions and pass others <q>up</q> to
+higher-level <code>try/except</code> blocks), you can use the
+ <code>.trap()</code> method in conjunction with multiple
+ <code>errback</code> handlers on the <code>Deferred</code>. Re-raising an
+exception in an <code>errback</code> handler serves to pass that new
+exception to the next handler in the chain. The <code>trap</code> method is
+given a list of exceptions to look for, and will re-raise anything that
+isn't on the list. Instead of passing unhandled exceptions <q>up</q> to an
+enclosing <code>try</code> block, this has the effect of passing the
+exception <q>off</q> to later <code>errback</code> handlers on the same
+<code>Deferred</code>. The <code>trap</code> calls are used in chained
+errbacks to test for each kind of exception in sequence. </p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyException</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Error</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_fooMethod</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">arg</span> == <span class="py-src-string">&quot;panic!&quot;</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">MyException</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;response&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">One</span>()))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/trap_server.py"><span class="filename">listings/pb/trap_server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyException</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Error</span>): <span class="py-src-keyword">pass</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyOtherException</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Error</span>): <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ScaryObject</span>:
+ <span class="py-src-comment"># not safe for serialization</span>
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">worksLike</span>(<span class="py-src-parameter">obj</span>):
+ <span class="py-src-comment"># the callback/errback sequence in class One works just like an</span>
+ <span class="py-src-comment"># asynchronous version of the following:</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">response</span> = <span class="py-src-variable">obj</span>.<span class="py-src-variable">callMethod</span>(<span class="py-src-variable">name</span>, <span class="py-src-variable">arg</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">DeadReferenceError</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; stale reference: the client disconnected or crashed&quot;</span>
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">jelly</span>.<span class="py-src-variable">InsecureJelly</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; InsecureJelly: you tried to send something unsafe to them&quot;</span>
+ <span class="py-src-keyword">except</span> (<span class="py-src-variable">MyException</span>, <span class="py-src-variable">MyOtherException</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; remote raised a MyException&quot;</span> <span class="py-src-comment"># or MyOtherException</span>
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; something else happened&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; method successful, response:&quot;</span>, <span class="py-src-variable">response</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">worked</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; method successful, response:&quot;</span>, <span class="py-src-variable">response</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">check_InsecureJelly</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
+ <span class="py-src-variable">failure</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">jelly</span>.<span class="py-src-variable">InsecureJelly</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; InsecureJelly: you tried to send something unsafe to them&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">check_MyException</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
+ <span class="py-src-variable">which</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">MyException</span>, <span class="py-src-variable">MyOtherException</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">which</span> == <span class="py-src-variable">MyException</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; remote raised a MyException&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; remote raised a MyOtherException&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">catch_everythingElse</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; something else happened&quot;</span>
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>(<span class="py-src-variable">failure</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">doCall</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">explanation</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">explanation</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">deferred</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;fooMethod&quot;</span>, <span class="py-src-variable">arg</span>)
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">worked</span>)
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">check_InsecureJelly</span>)
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">check_MyException</span>)
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">catch_everythingElse</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">DeadReferenceError</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; stale reference: the client disconnected or crashed&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callOne</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">doCall</span>(<span class="py-src-string">&quot;callOne: call with safe object&quot;</span>, <span class="py-src-string">&quot;safe string&quot;</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callTwo</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">doCall</span>(<span class="py-src-string">&quot;callTwo: call with dangerous object&quot;</span>, <span class="py-src-variable">ScaryObject</span>())
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callThree</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">doCall</span>(<span class="py-src-string">&quot;callThree: call that raises remote exception&quot;</span>, <span class="py-src-string">&quot;panic!&quot;</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callShutdown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;telling them to shut down&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;shutdown&quot;</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callFour</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">doCall</span>(<span class="py-src-string">&quot;callFour: call on stale reference&quot;</span>, <span class="py-src-string">&quot;dummy&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">obj</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">obj</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">1</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">callOne</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">2</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">callTwo</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">3</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">callThree</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">4</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">callShutdown</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">callFour</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">6</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>)
+
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+<span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">One</span>().<span class="py-src-variable">got_obj</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/trap_client.py"><span class="filename">listings/pb/trap_client.py</span></a></div></div>
+
+<pre class="shell" xml:space="preserve">
+$ ./trap_client.py
+callOne: call with safe object
+ method successful, response: response
+callTwo: call with dangerous object
+ InsecureJelly: you tried to send something unsafe to them
+callThree: call that raises remote exception
+ remote raised a MyException
+telling them to shut down
+callFour: call on stale reference
+ stale reference: the client disconnected or crashed
+</pre>
+
+
+<p>In this example, <code>callTwo</code> tries to send an instance of a
+locally-defined class through <code>callRemote</code>. The default security
+model implemented by <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.html" title="twisted.spread.jelly">jelly</a></code>
+on the remote end will not allow unknown classes to be unserialized (i.e.
+taken off the wire as a stream of bytes and turned back into an object: a
+living, breathing instance of some class): one reason is that it does not
+know which local class ought to be used to create an instance that
+corresponds to the remote object<a href="#footnote-5" title="The naive approach of simply doing import SomeClass to match a remote caller who claims to have an object of type SomeClass could have nasty consequences for some modules that do significant operations in their __init__ methods (think telnetlib.Telnet(host='localhost', port='chargen'), or even more powerful classes that you have available in your server program). Allowing a remote entity to create arbitrary classes in your namespace is nearly equivalent to allowing them to run arbitrary code. The InsecureJelly exception arises because the class being sent over the wire has not been registered with the serialization layer (known as jelly). The easiest way to make it possible to copy entire class instances over the wire is to have them inherit from pb.Copyable, and then to use setUnjellyableForClass(remoteClass, localClass) on the receiving side. See Passing Complex Types for an example."><super>5</super></a>.</p>
+
+<p>The receiving end of the connection gets to decide what to accept and what
+to reject. It indicates its disapproval by raising a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.InsecureJelly.html" title="twisted.spread.jelly.InsecureJelly">jelly.InsecureJelly</a></code> exception. Because it occurs
+at the remote end, the exception is returned to the caller asynchronously,
+so an <code>errback</code> handler for the associated <code>Deferred</code>
+is run. That errback receives a <code>Failure</code> which wraps the
+ <code>InsecureJelly</code>.</p>
+
+
+<p>Remember that <code>trap</code> re-raises exceptions that it wasn't asked
+to look for. You can only check for one set of exceptions per errback
+handler: all others must be checked in a subsequent handler.
+ <code>check_MyException</code> shows how multiple kinds of exceptions can be
+checked in a single errback: give a list of exception types to
+ <code>trap</code>, and it will return the matching member. In this case, the
+kinds of exceptions we are checking for (<code>MyException</code> and
+ <code>MyOtherException</code>) may be raised by the remote end: they inherit
+from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Error.html" title="twisted.spread.pb.Error">pb.Error</a></code>.</p>
+
+<p>The handler can return <code>None</code> to terminate processing of the
+errback chain (to be precise, it switches to the callback that follows the
+errback; if there is no callback then processing terminates). It is a good
+idea to put an errback that will catch everything (no <code>trap</code>
+tests, no possible chance of raising more exceptions, always returns
+ <code>None</code>) at the end of the chain. Just as with regular <code>try:
+except:</code> handlers, you need to think carefully about ways in which
+your errback handlers could themselves raise exceptions. The extra
+importance in an asynchronous environment is that an exception that falls
+off the end of the <code>Deferred</code> will not be signalled until that
+ <code>Deferred</code> goes out of scope, and at that point may only cause a
+log message (which could even be thrown away if <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.startLogging.html" title="twisted.python.log.startLogging">log.startLogging</a></code> is not used to point it at
+stdout or a log file). In contrast, a synchronous exception that is not
+handled by any other <code>except:</code> block will very visibly terminate
+the program immediately with a noisy stack trace.</p>
+
+<p><code>callFour</code> shows another kind of exception that can occur
+while using <code>callRemote</code>: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.DeadReferenceError.html" title="twisted.spread.pb.DeadReferenceError">pb.DeadReferenceError</a></code>. This one occurs when the
+remote end has disconnected or crashed, leaving the local side with a stale
+reference. This kind of exception happens to be reported right away (XXX: is
+this guaranteed? probably not), so must be caught in a traditional
+synchronous <code>try: except pb.DeadReferenceError</code> block. </p>
+
+<p>Yet another kind that can occur is a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.PBConnectionLost.html" title="twisted.spread.pb.PBConnectionLost">pb.PBConnectionLost</a></code> exception. This occurs
+(asynchronously) if the connection was lost while you were waiting for a
+<code>callRemote</code> call to complete. When the line goes dead, all
+pending requests are terminated with this exception. Note that you have no
+way of knowing whether the request made it to the other end or not, nor how
+far along in processing it they had managed before the connection was
+lost. XXX: explain transaction semantics, find a decent reference.</p>
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">There are a few other classes
+that can bestow this ability, but pb.Referenceable is the easiest to
+understand; see 'flavors' below for details on the others.</span></a></li><li><a name="footnote-2"><span class="footnote">This can be overridden, by subclassing one of
+the Serializable flavors and defining custom serialization code for your
+class. See <a href="pb-copyable.html" shape="rect">Passing Complex Types</a> for
+details.</span></a></li><li><a name="footnote-3"><span class="footnote">The binary nature of this
+local vs. remote scheme works because you cannot give RemoteReferences to a
+third party. If you could, then your object A could go to B, B could give it to
+C, C might give it back to you, and you would be hard pressed to tell if the
+object lived in C's memory space, in B's, or if it was really your own object,
+tarnished and sullied after being handed down like a really ugly picture that
+your great aunt owned and which nobody wants but which nobody can bear to throw
+out. Ok, not really like that, but you get the idea.</span></a></li><li><a name="footnote-4"><span class="footnote">To be precise,
+the Failure will be sent if <em>any</em> exception is raised, not just
+pb.Error-derived ones. But the server will print ugly error messages if you
+raise ones that aren't derived from pb.Error.</span></a></li><li><a name="footnote-5"><span class="footnote"><p>The naive approach
+of simply doing <code>import SomeClass</code> to match a remote caller who
+claims to have an object of type <q>SomeClass</q> could have nasty consequences
+for some modules that do significant operations in their <code>__init__</code>
+methods (think <code>telnetlib.Telnet(host='localhost', port='chargen')</code>,
+ or even more powerful classes that you have available in your server program).
+Allowing a remote entity to create arbitrary classes in your namespace is
+nearly equivalent to allowing them to run arbitrary code.</p>
+<p>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.InsecureJelly.html" title="twisted.spread.jelly.InsecureJelly">InsecureJelly</a></code>
+exception arises because the class being sent over the wire has not been
+registered with the serialization layer (known as <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.html" title="twisted.spread.jelly">jelly</a></code>). The easiest way to make it possible to
+copy entire class instances over the wire is to have them inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>, and then to use
+ <code>setUnjellyableForClass(remoteClass, localClass)</code> on the
+receiving side. See <a href="pb-copyable.html" shape="rect">Passing Complex Types</a>
+for an example.</p></span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/pb.html b/doc/core/howto/pb.html
new file mode 100644
index 0000000..f610dee
--- /dev/null
+++ b/doc/core/howto/pb.html
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Overview of Twisted Spread</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Overview of Twisted Spread</h1>
+ <div class="toc"><ol><li><a href="#auto0">Rationale</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p> Perspective Broker (affectionately known as <q>PB</q>) is an
+asynchronous, symmetric<a href="#footnote-1" title="There is a negotiation phase for the banana serialization protocol with particular roles for listener and initiator, so it's not completely symmetric, but after the connection is fully established, the protocol is completely symmetrical."><super>1</super></a> network protocol for secure,
+remote method calls and transferring of objects. PB is <q>translucent, not
+transparent</q>, meaning that it is very visible and obvious to see the
+difference between local method calls and potentially remote method calls,
+but remote method calls are still extremely convenient to make, and it is
+easy to emulate them to have objects which work both locally and
+remotely.</p>
+
+<p>PB supports user-defined serialized data in return values, which can be
+either copied each time the value is returned, or <q>cached</q>: only copied
+once and updated by notifications.</p>
+
+<p>PB gets its name from the fact that access to objects is through a
+<q>perspective</q>. This means that when you are responding to a remote
+method call, you can establish who is making the call.</p>
+
+<h2>Rationale<a name="auto0"/></h2>
+
+<p>No other currently existing protocols have all the properties of PB at the
+same time. The particularly interesting combination of attributes, though, is
+that PB is flexible and lightweight, allowing for rapid development, while
+still powerful enough to do two-way method calls and user-defined data
+types.</p>
+
+<p>It is important to have these attributes in order to allow for a protocol
+which is extensible. One of the facets of this flexibility is that PB can
+integrate an arbitrary number of services could be aggregated over a single
+connection, as well as publish and call new methods on existing objects
+without restarting the server or client.</p>
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">There is a negotiation phase
+for the <code>banana</code> serialization protocol with particular roles for listener and initiator, so it's not
+<em>completely</em> symmetric, but after the connection is fully established,
+the protocol is completely symmetrical.</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/plugin.html b/doc/core/howto/plugin.html
new file mode 100644
index 0000000..9aacdaa
--- /dev/null
+++ b/doc/core/howto/plugin.html
@@ -0,0 +1,294 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Twisted Plugin System</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Twisted Plugin System</h1>
+ <div class="toc"><ol><li><a href="#auto0">Writing Extensible Programs</a></li><li><a href="#auto1">Extending an Existing Program</a></li><li><a href="#auto2">Alternate Plugin Packages</a></li><li><a href="#auto3">Plugin Caching</a></li><li><a href="#auto4">Further Reading</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <p>The purpose of this guide is to describe the preferred way to
+ write extensible Twisted applications (and consequently, also to
+ describe how to extend applications written in such a way). This
+ extensibility is achieved through the definition of one or more
+ APIs and a mechanism for collecting code plugins which
+ implement this API to provide some additional functionality.
+ At the base of this system is the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.plugin.html" title="twisted.plugin">twisted.plugin</a></code> module.</p>
+
+ <p>Making an application extensible using the plugin system has
+ several strong advantages over other techniques:</p>
+
+ <ul>
+ <li>It allows third-party developers to easily enhance your
+ software in a way that is loosely coupled: only the plugin API
+ is required to remain stable.</li>
+
+ <li>It allows new plugins to be discovered flexibly. For
+ example, plugins can be loaded and saved when a program is first
+ run, or re-discovered each time the program starts up, or they
+ can be polled for repeatedly at runtime (allowing the discovery
+ of new plugins installed after the program has started).</li>
+ </ul>
+
+ <h2>Writing Extensible Programs<a name="auto0"/></h2>
+
+ <p>Taking advantage of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.plugin.html" title="twisted.plugin">twisted.plugin</a></code> is
+ a two step process:</p>
+
+ <ol>
+ <li>
+ <p>
+ Define an interface which plugins will be required to implement.
+ This is done using the zope.interface package in the same way one
+ would define an interface for any other purpose.
+ </p>
+
+ <p>
+ A convention for defining interfaces is do so in a file named like
+ <em>ProjectName/projectname/iprojectname.py</em>. The rest of this
+ document will follow that convention: consider the following
+ interface definition be in <code>Matsim/matsim/imatsim.py</code>, an
+ interface definition module for a hypothetical material simulation
+ package.
+ </p>
+ </li>
+
+ <li>
+ At one or more places in your program, invoke <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.plugin.getPlugins.html" title="twisted.plugin.getPlugins">twisted.plugin.getPlugins</a></code> and iterate over its
+ result.
+ </li>
+ </ol>
+
+ <p>
+ As an example of the first step, consider the following interface
+ definition for a physical modelling system.
+ </p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">Attribute</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMaterial</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ An object with specific physical properties
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">yieldStress</span>(<span class="py-src-parameter">temperature</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Returns the pressure this material can support without
+ fracturing at the given temperature.
+
+ @type temperature: C{float}
+ @param temperature: Kelvins
+
+ @rtype: C{float}
+ @return: Pascals
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-variable">dielectricConstant</span> = <span class="py-src-variable">Attribute</span>(<span class="py-src-string">&quot;&quot;&quot;
+ @type dielectricConstant: C{complex}
+ @ivar dielectricConstant: The relative permittivity, with the
+ real part giving reflective surface properties and the
+ imaginary part giving the radio absorption coefficient.
+ &quot;&quot;&quot;</span>)
+</pre>
+
+ <p>In another module, we might have a function that operates on
+ objects providing the <code>IMaterial</code> interface:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">displayMaterial</span>(<span class="py-src-parameter">m</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'A material with yield stress %s at 500 K'</span> % (<span class="py-src-variable">m</span>.<span class="py-src-variable">yieldStress</span>(<span class="py-src-number">500</span>),)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Also a dielectric constant of %s.'</span> % (<span class="py-src-variable">m</span>.<span class="py-src-variable">dielectricConstant</span>,)
+</pre>
+
+ <p>The last piece of required code is that which collects
+ <code>IMaterial</code> providers and passes them to the
+ <code>displayMaterial</code> function.</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">getPlugins</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">matsim</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">imatsim</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">displayAllKnownMaterials</span>():
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">material</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">getPlugins</span>(<span class="py-src-variable">imatsim</span>.<span class="py-src-variable">IMaterial</span>):
+ <span class="py-src-variable">displayMaterial</span>(<span class="py-src-variable">material</span>)
+</pre>
+
+ <p>Third party developers may now contribute different materials
+ to be used by this modelling system by implementing one or more
+ plugins for the <code>IMaterial</code> interface.</p>
+
+ <h2>Extending an Existing Program<a name="auto1"/></h2>
+
+ <p>The above code demonstrates how an extensible program might be
+ written using Twisted's plugin system. How do we write plugins
+ for it, though? Essentially, we create objects which provide the
+ required interface and then make them available at a particular
+ location. Consider the following example.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">matsim</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">imatsim</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleMaterial</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPlugin</span>, <span class="py-src-variable">imatsim</span>.<span class="py-src-variable">IMaterial</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">yieldStressFactor</span>, <span class="py-src-parameter">dielectricConstant</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_yieldStressFactor</span> = <span class="py-src-variable">yieldStressFactor</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">dielectricConstant</span> = <span class="py-src-variable">dielectricConstant</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">yieldStress</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">temperature</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_yieldStressFactor</span> * <span class="py-src-variable">temperature</span>
+
+<span class="py-src-variable">steelPlate</span> = <span class="py-src-variable">SimpleMaterial</span>(<span class="py-src-number">2.06842719e11</span>, <span class="py-src-number">2.7</span> + <span class="py-src-number">0.2j</span>)
+<span class="py-src-variable">brassPlate</span> = <span class="py-src-variable">SimpleMaterial</span>(<span class="py-src-number">1.03421359e11</span>, <span class="py-src-number">1.4</span> + <span class="py-src-number">0.5j</span>)
+</pre>
+
+ <p><code>steelPlate</code> and <code>brassPlate</code> now provide both
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.plugin.IPlugin.html" title="twisted.plugin.IPlugin">IPlugin</a></code> and <code>IMaterial</code>.
+ All that remains is to make this module available at an appropriate
+ location. For this, there are two options. The first of these is
+ primarily useful during development: if a directory which
+ has been added to <code>sys.path</code> (typically by adding it to the
+ <code class="shell">PYTHONPATH</code> environment variable) contains a
+ <em>directory</em> named <code class="shell">twisted/plugins/</code>,
+ each <code class="shell">.py</code> file in that directory will be loaded
+ as a source of plugins. This directory <em>must not</em> be a Python
+ package: including <code class="shell">__init__.py</code> will cause the
+ directory to be skipped and no plugins loaded from it. Second, each
+ module in the installed version of Twisted's <code class="shell">
+ twisted.plugins</code> package will also be loaded as a source of
+ plugins.</p>
+
+ <p>Once this plugin is installed in one of these two ways,
+ <code>displayAllKnownMaterials</code> can be run and we will see
+ two pairs of output: one for a steel plate and one for a brass
+ plate.</p>
+
+ <h2>Alternate Plugin Packages<a name="auto2"/></h2>
+
+ <p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.plugin.getPlugins.html" title="twisted.plugin.getPlugins">getPlugins</a></code> takes one
+ additional argument not mentioned above. If passed in, the 2nd argument
+ should be a module or package to be used instead of
+ <code>twisted.plugins</code> as the plugin meta-package. If you
+ are writing a plugin for a Twisted interface, you should never
+ need to pass this argument. However, if you have developed an
+ interface of your own, you may want to mandate that plugins for it
+ are installed in your own plugins package, rather than in
+ Twisted's.</p>
+
+ <p>You may want to support <code class="shell">yourproject/plugins/</code>
+ directories for ease of development. To do so, you should make <code class="shell">yourproject/plugins/__init__.py</code> contain at least
+ the following lines.</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pluginPackagePaths</span>
+<span class="py-src-variable">__path__</span>.<span class="py-src-variable">extend</span>(<span class="py-src-variable">pluginPackagePaths</span>(<span class="py-src-variable">__name__</span>))
+<span class="py-src-variable">__all__</span> = []
+</pre>
+
+ <p>The key behavior here is that interfaces are essentially paired
+ with a particular plugin package. If plugins are installed in a
+ different package than the one the code which relies on the
+ interface they provide, they will not be found when the
+ application goes to load them.</p>
+
+ <h2>Plugin Caching<a name="auto3"/></h2>
+
+ <p>In the course of using the Twisted plugin system, you may
+ notice <code class="shell">dropin.cache</code> files appearing at
+ various locations. These files are used to cache information
+ about what plugins are present in the directory which contains
+ them. At times, this cached information may become out of date.
+ Twisted uses the mtimes of various files involved in the plugin
+ system to determine when this cache may have become invalid.
+ Twisted will try to re-write the cache each time it tries to use
+ it but finds it out of date.</p>
+
+ <p>For a site-wide install, it may not (indeed, should not) be
+ possible for applications running as normal users to rewrite the
+ cache file. While these applications will still run and find
+ correct plugin information, they may run more slowly than they
+ would if the cache was up to date, and they may also report
+ exceptions if certain plugins have been removed but which the
+ cache still references. For these reasons, when installing or
+ removing software which provides Twisted plugins, the site
+ administrator should be sure the cache is regenerated.
+ Well-behaved package managers for such software should take this
+ task upon themselves, since it is trivially automatable. The
+ canonical way to regenerate the cache is to run the following
+ Python code:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>, <span class="py-src-variable">getPlugins</span>
+<span class="py-src-variable">list</span>(<span class="py-src-variable">getPlugins</span>(<span class="py-src-variable">IPlugin</span>))
+</pre>
+
+ <p>As mentioned, it is normal for exceptions to be raised
+ <strong>once</strong> here if plugins have been removed.</p>
+
+ <h2>Further Reading<a name="auto4"/></h2>
+
+ <ul>
+
+ <li><a href="components.html" shape="rect">Components: Interfaces and Adapters</a></li>
+
+ </ul>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/process.html b/doc/core/howto/process.html
new file mode 100644
index 0000000..ed04ab0
--- /dev/null
+++ b/doc/core/howto/process.html
@@ -0,0 +1,732 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using Processes</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using Processes</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Running Another Process</a></li><li><a href="#auto2">Writing a ProcessProtocol</a></li><li><a href="#auto3">Things that can happen to your ProcessProtocol</a></li><li><a href="#auto4">Things you can do from your ProcessProtocol</a></li><li><a href="#auto5">Verbose Example</a></li><li><a href="#auto6">Doing it the Easy Way</a></li><li><a href="#auto7">Mapping File Descriptors</a></li><ul><li><a href="#auto8">ProcessProtocols with extra file descriptors</a></li><li><a href="#auto9">Examples</a></li></ul></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+<p>Along with connection to servers across the internet, Twisted also
+connects to local processes with much the same API. The API is described in
+more detail in the documentation of:
+<ul>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorProcess.html" title="twisted.internet.interfaces.IReactorProcess">twisted.internet.interfaces.IReactorProcess</a></code></li>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProcessTransport.html" title="twisted.internet.interfaces.IProcessTransport">twisted.internet.interfaces.IProcessTransport</a></code></li>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProcessProtocol.html" title="twisted.internet.interfaces.IProcessProtocol">twisted.internet.interfaces.IProcessProtocol</a></code></li>
+</ul>
+</p>
+
+ <h2>Running Another Process<a name="auto1"/></h2>
+
+<p>Processes are run through the reactor,
+using <code>reactor.spawnProcess</code>. Pipes are created to the child process,
+and added to the reactor core so that the application will not block while
+sending data into or pulling data out of the new
+process. <code>reactor.spawnProcess</code> requires two arguments,
+ <code>processProtocol</code> and <code>executable</code>, and optionally takes
+several more: <code>args</code>, <code>environment</code>,
+ <code>path</code>, <code>userID</code>, <code>groupID</code>,
+ <code>usePTY</code>, and <code>childFDs</code>. Not all of these are
+available on Windows.</p>
+
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">processProtocol</span> = <span class="py-src-variable">MyProcessProtocol</span>()
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">spawnProcess</span>(<span class="py-src-variable">processProtocol</span>, <span class="py-src-variable">executable</span>, <span class="py-src-variable">args</span>=[<span class="py-src-variable">program</span>, <span class="py-src-variable">arg1</span>, <span class="py-src-variable">arg2</span>],
+ <span class="py-src-variable">env</span>={<span class="py-src-string">'HOME'</span>: <span class="py-src-variable">os</span>.<span class="py-src-variable">environ</span>[<span class="py-src-string">'HOME'</span>]}, <span class="py-src-variable">path</span>,
+ <span class="py-src-variable">uid</span>, <span class="py-src-variable">gid</span>, <span class="py-src-variable">usePTY</span>, <span class="py-src-variable">childFDs</span>)
+</pre>
+
+<ul>
+
+ <li><code>processProtocol</code> should be an instance of a subclass of
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.ProcessProtocol.html" title="twisted.internet.protocol.ProcessProtocol">twisted.internet.protocol.ProcessProtocol</a></code>. The
+ interface is described below.</li>
+
+ <li><code>executable</code> is the full path of the program to run. It
+ will be connected to processProtocol.</li>
+
+ <li><code>args</code> is a list of command line arguments to be passed to
+ the process. <code>args[0]</code> should be the name of the process.</li>
+
+ <li><code>env</code> is a dictionary containing the environment to pass
+ through to the process.</li>
+
+ <li><code>path</code> is the directory to run the process in. The child
+ will switch to the given directory just before starting the new program.
+ The default is to stay in the current directory.</li>
+
+ <li><code>uid</code> and <code>gid</code> are the user ID and group ID to
+ run the subprocess as. Of course, changing identities will be more likely
+ to succeed if you start as root.</li>
+
+ <li><code>usePTY</code> specifies whether the child process should be run
+ with a pty, or if it should just get a pair of pipes. Whether a program
+ needs to be run with a PTY or not depends on the particulars of that
+ program. Often, programs which primarily interact with users via a terminal
+ do need a PTY.</li>
+
+ <li><code>childFDs</code> lets you specify how the child's file
+ descriptors should be set up. Each key is a file descriptor number (an
+ integer) as seen by the child. 0, 1, and 2 are usually stdin, stdout, and
+ stderr, but some programs may be instructed to use additional fds through
+ command-line arguments or environment variables. Each value is either an
+ integer specifying one of the parent's current file descriptors, the
+ string <q>r</q> which creates a pipe that the parent can read from, or the
+ string <q>w</q> which creates a pipe that the parent can write to. If
+ <code>childFDs</code> is not provided, a default is used which creates the
+ usual stdin-writer, stdout-reader, and stderr-reader pipes.</li>
+
+</ul>
+
+<p><code>args</code> and <code>env</code> have empty default values, but
+many programs depend upon them to be set correctly. At the very least,
+ <code>args[0]</code> should probably be the same as <code>executable</code>.
+If you just provide <code>os.environ</code> for <code>env</code>, the child
+program will inherit the environment from the current process, which is
+usually the civilized thing to do (unless you want to explicitly clean the
+environment as a security precaution). The default is to give an empty
+<code>env</code> to the child.</p>
+
+<p><code>reactor.spawnProcess</code> returns an instance that
+implements
+<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProcessTransport.html" title="twisted.internet.interfaces.IProcessTransport">IProcessTransport</a></code>.
+</p>
+
+ <h2>Writing a ProcessProtocol<a name="auto2"/></h2>
+
+<p>The ProcessProtocol you pass to <code>spawnProcess</code> is your
+interaction with the process. It has a very similar signature to a regular
+Protocol, but it has several extra methods to deal with events specific to
+a process. In our example, we will interface with 'wc' to create a word count
+of user-given text. First, we'll start by importing the required modules, and
+writing the initialization for our ProcessProtocol.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">WCProcessProtocol</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ProcessProtocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">text</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">text</span> = <span class="py-src-variable">text</span>
+</pre>
+
+<p>When the ProcessProtocol is connected to the protocol, it has the
+connectionMade method called. In our protocol, we will write our text to the
+standard input of our process and then close standard input, to let the
+process know we are done writing to it.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p>...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">text</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">closeStdin</span>()
+</pre>
+
+<p>At this point, the process has receieved the data, and it's time for us
+to read the results. Instead of being received in <code>dataReceived</code>,
+data from standard output is received in <code>outReceived</code>. This is
+to distinguish it from data on standard error.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p>...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">outReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">fieldLength</span> = <span class="py-src-variable">len</span>(<span class="py-src-variable">data</span>) / <span class="py-src-number">3</span>
+ <span class="py-src-variable">lines</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">data</span>[:<span class="py-src-variable">fieldLength</span>])
+ <span class="py-src-variable">words</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">data</span>[<span class="py-src-variable">fieldLength</span>:<span class="py-src-variable">fieldLength</span>*<span class="py-src-number">2</span>])
+ <span class="py-src-variable">chars</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">data</span>[<span class="py-src-variable">fieldLength</span>*<span class="py-src-number">2</span>:])
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">receiveCounts</span>(<span class="py-src-variable">lines</span>, <span class="py-src-variable">words</span>, <span class="py-src-variable">chars</span>)
+</pre>
+
+<p>Now, the process has parsed the output, and ended the connection to the
+process. Then it sends the results on to the final method, receiveCounts.
+This is for users of the class to override, so as to do other things with
+the data. For our demonstration, we will just print the results.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p>...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">receiveCounts</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">lines</span>, <span class="py-src-parameter">words</span>, <span class="py-src-parameter">chars</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Received counts from wc.'</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Lines:'</span>, <span class="py-src-variable">lines</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Words:'</span>, <span class="py-src-variable">words</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Characters:'</span>, <span class="py-src-variable">chars</span>
+</pre>
+
+<p>We're done! To use our WCProcessProtocol, we create an instance, and pass
+it to spawnProcess.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-variable">wcProcess</span> = <span class="py-src-variable">WCProcessProtocol</span>(<span class="py-src-string">&quot;accessing protocols through Twisted is fun!\n&quot;</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">spawnProcess</span>(<span class="py-src-variable">wcProcess</span>, <span class="py-src-string">'wc'</span>, [<span class="py-src-string">'wc'</span>])
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+
+<h2>Things that can happen to your ProcessProtocol<a name="auto3"/></h2>
+
+<p>These are the methods that you can usefully override in your subclass of
+ <code>ProcessProtocol</code>:</p>
+
+<ul>
+
+ <li><code>.connectionMade()</code>: This is called when the program is
+ started, and makes a good place to write data into the stdin pipe (using
+ <code class="python">self.transport.write</code>).</li>
+
+ <li><code>.outReceived(data)</code>: This is called with data that was
+ received from the process' stdout pipe. Pipes tend to provide data in
+ larger chunks than sockets (one kilobyte is a common buffer size), so you
+ may not experience the <q>random dribs and drabs</q> behavior typical of
+ network sockets, but regardless you should be prepared to deal if you
+ don't get all your data in a single call. To do it properly,
+ <code>outReceived</code> ought to simply accumulate the data and put off
+ doing anything with it until the process has finished.</li>
+
+ <li><code>.errReceived(data)</code>: This is called with data from the
+ process' stderr pipe. It behaves just like <code>outReceived</code>.</li>
+
+ <li><code>.inConnectionLost</code>: This is called when the reactor notices
+ that the process' stdin pipe has closed. Programs don't typically close
+ their own stdin, so this will probably get called when your
+ ProcessProtocol has shut down the write side with <code class="python">self.transport.loseConnection</code>.</li>
+
+ <li><code>.outConnectionLost</code>: This is called when the program closes
+ its stdout pipe. This usually happens when the program terminates.</li>
+
+ <li><code>.errConnectionLost</code>: Same as
+ <code>outConnectionLost</code>, but for stderr instead of stdout.</li>
+
+ <li><code>.processExited(status)</code>: This is called when the child
+ process has been reaped, and receives information about the process' exit
+ status. The status is passed in the form of a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code> instance, created with a
+ <code>.value</code> that either holds a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.error.ProcessDone.html" title="twisted.internet.error.ProcessDone">ProcessDone</a></code> object if the process
+ terminated normally (it died of natural causes instead of receiving a
+ signal, and if the exit code was 0), or a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.error.ProcessTerminated.html" title="twisted.internet.error.ProcessTerminated">ProcessTerminated</a></code> object (with an
+ <code>.exitCode</code> attribute) if something went wrong.</li>
+
+ <li><code>.processEnded(status)</code>: This is called when all the file
+ descriptors associated with the child process have been closed and the
+ process has been reaped. This means it is the last callback which will be
+ made onto a <code>ProcessProtocol</code>. The <code>status</code> parameter
+ has the same meaning as it does for <code>processExited</code>.</li>
+
+</ul>
+
+<p>The base-class definitions of most of these functions are no-ops. This will
+result in all stdout and stderr being thrown away. Note that it is important
+for data you don't care about to be thrown away: if the pipe were not read,
+the child process would eventually block as it tried to write to a full
+pipe.</p>
+
+
+<h2>Things you can do from your ProcessProtocol<a name="auto4"/></h2>
+
+<p>The following are the basic ways to control the child process:</p>
+
+<ul>
+
+ <li><code>self.transport.write(data)</code>: Stuff some data in the stdin
+ pipe. Note that this <code>write</code> method will queue any data that can't
+ be written immediately. Writing will resume in the future when the pipe
+ becomes writable again.</li>
+
+ <li><code>self.transport.closeStdin</code>: Close the stdin pipe. Programs
+ which act as filters (reading from stdin, modifying the data, writing to
+ stdout) usually take this as a sign that they should finish their job and
+ terminate. For these programs, it is important to close stdin when you're
+ done with it, otherwise the child process will never quit.</li>
+
+ <li><code>self.transport.closeStdout</code>: Not usually called, since you're
+ putting the process into a state where any attempt to write to stdout will
+ cause a SIGPIPE error. This isn't a nice thing to do to the poor
+ process.</li>
+
+ <li><code>self.transport.closeStderr</code>: Not usually called, same reason
+ as <code>closeStdout</code>.</li>
+
+ <li><code>self.transport.loseConnection</code>: Close all three pipes.</li>
+
+ <li><code>self.transport.signalProcess('KILL')</code>: Kill the child
+ process. This will eventually result in <code>processEnded</code> being
+ called.</li>
+
+</ul>
+
+
+<h2>Verbose Example<a name="auto5"/></h2>
+
+<p>Here is an example that is rather verbose about exactly when all the
+methods are called. It writes a number of lines into the <code>wc</code>
+program and then parses the output.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">re</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyPP</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ProcessProtocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">verses</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">verses</span> = <span class="py-src-variable">verses</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">data</span> = <span class="py-src-string">&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;connectionMade!&quot;</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">i</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">range</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">verses</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Aleph-null bottles of beer on the wall,\n&quot;</span> +
+ <span class="py-src-string">&quot;Aleph-null bottles of beer,\n&quot;</span> +
+ <span class="py-src-string">&quot;Take one down and pass it around,\n&quot;</span> +
+ <span class="py-src-string">&quot;Aleph-null bottles of beer on the wall.\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">closeStdin</span>() <span class="py-src-comment"># tell them we're done</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">outReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;outReceived! with %d bytes!&quot;</span> % <span class="py-src-variable">len</span>(<span class="py-src-variable">data</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">data</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">data</span> + <span class="py-src-variable">data</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">errReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errReceived! with %d bytes!&quot;</span> % <span class="py-src-variable">len</span>(<span class="py-src-variable">data</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">inConnectionLost</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;inConnectionLost! stdin is closed! (we probably did it)&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">outConnectionLost</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;outConnectionLost! The child closed their stdout!&quot;</span>
+ <span class="py-src-comment"># now is the time to examine what they wrote</span>
+ <span class="py-src-comment">#print &quot;I saw them write:&quot;, self.data</span>
+ (<span class="py-src-variable">dummy</span>, <span class="py-src-variable">lines</span>, <span class="py-src-variable">words</span>, <span class="py-src-variable">chars</span>, <span class="py-src-variable">file</span>) = <span class="py-src-variable">re</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">r'\s+'</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">data</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I saw %s lines&quot;</span> % <span class="py-src-variable">lines</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">errConnectionLost</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errConnectionLost! The child closed their stderr.&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">processExited</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;processExited, status %d&quot;</span> % (<span class="py-src-variable">reason</span>.<span class="py-src-variable">value</span>.<span class="py-src-variable">exitCode</span>,)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">processEnded</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;processEnded, status %d&quot;</span> % (<span class="py-src-variable">reason</span>.<span class="py-src-variable">value</span>.<span class="py-src-variable">exitCode</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;quitting&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">pp</span> = <span class="py-src-variable">MyPP</span>(<span class="py-src-number">10</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">spawnProcess</span>(<span class="py-src-variable">pp</span>, <span class="py-src-string">&quot;wc&quot;</span>, [<span class="py-src-string">&quot;wc&quot;</span>], {})
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/process/process.py"><span class="filename">listings/process/process.py</span></a></div></div>
+
+<p>The exact output of this program depends upon the relative timing of some
+un-synchronized events. In particular, the program may observe the child
+process close its stderr pipe before or after it reads data from the stdout
+pipe. One possible transcript would look like this:</p>
+
+<pre class="shell" xml:space="preserve">
+% ./process.py
+connectionMade!
+inConnectionLost! stdin is closed! (we probably did it)
+errConnectionLost! The child closed their stderr.
+outReceived! with 24 bytes!
+outConnectionLost! The child closed their stdout!
+I saw 40 lines
+processEnded, status 0
+quitting
+Main loop terminated.
+%
+</pre>
+
+<h2>Doing it the Easy Way<a name="auto6"/></h2>
+
+<p>Frequently, one just needs a simple way to get all the output from a
+program. In the blocking world, you might use <code class="python">commands.getoutput</code> from the standard library, but
+using that in an event-driven program will cause everything else to stall
+until the command finishes. (in addition, the SIGCHLD handler used by that
+function does not play well with Twisted's own signal handling). For these
+cases, the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.utils.getProcessOutput.html" title="twisted.internet.utils.getProcessOutput">twisted.internet.utils.getProcessOutput</a></code>
+function can be used. Here is a simple example:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">utils</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">cStringIO</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">StringIO</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FortuneQuoter</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-variable">fortune</span> = <span class="py-src-string">'/usr/games/fortune'</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">output</span> = <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessOutput</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">fortune</span>)
+ <span class="py-src-variable">output</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">writeResponse</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">noResponse</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">resp</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">resp</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">noResponse</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">err</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">Factory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FortuneQuoter</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">10999</span>, <span class="py-src-variable">f</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/process/quotes.py"><span class="filename">listings/process/quotes.py</span></a></div></div>
+
+<p>If you only need the final exit code (like <code class="python">commands.getstatusoutput(cmd)[0]</code>), the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.utils.getProcessValue.html" title="twisted.internet.utils.getProcessValue">twisted.internet.utils.getProcessValue</a></code> function is
+useful. Here is an example:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">utils</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printTrueValue</span>(<span class="py-src-parameter">val</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;/bin/true exits with rc=%d&quot;</span> % <span class="py-src-variable">val</span>
+ <span class="py-src-variable">output</span> = <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessValue</span>(<span class="py-src-string">'/bin/false'</span>)
+ <span class="py-src-variable">output</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printFalseValue</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printFalseValue</span>(<span class="py-src-parameter">val</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;/bin/false exits with rc=%d&quot;</span> % <span class="py-src-variable">val</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">output</span> = <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessValue</span>(<span class="py-src-string">'/bin/true'</span>)
+<span class="py-src-variable">output</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printTrueValue</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/process/trueandfalse.py"><span class="filename">listings/process/trueandfalse.py</span></a></div></div>
+
+<h2>Mapping File Descriptors<a name="auto7"/></h2>
+
+<p><q>stdin</q>, <q>stdout</q>, and <q>stderr</q> are just conventions.
+Programs which operate as filters generally accept input on fd0, write their
+output on fd1, and emit error messages on fd2. This is common enough that
+the standard C library provides macros like <q>stdin</q> to mean fd0, and
+shells interpret the pipe character <q>|</q> to mean <q>redirect fd1 from
+one command into fd0 of the next command</q>.</p>
+
+<p>But these are just conventions, and programs are free to use additional
+file descriptors or even ignore the standard three entirely. The
+<q>childFDs</q> argument allows you to specify exactly what kind of files
+descriptors the child process should be given.</p>
+
+<p>Each child FD can be put into one of three states:</p>
+
+<ul>
+ <li>Mapped to a parent FD: this causes the child's reads and writes to
+ come from or go to the same source/destination as the parent.</li>
+
+ <li>Feeding into a pipe which can be read by the parent.</li>
+
+ <li>Feeding from a pipe which the parent writes into.</li>
+</ul>
+
+<p>Mapping the child FDs to the parent's is very commonly used to send the
+child's stderr output to the same place as the parent's. When you run a
+program from the shell, it will typically leave fds 0, 1, and 2 mapped to
+the shell's 0, 1, and 2, allowing you to see the child program's output on
+the same terminal you used to launch the child. Likewise, inetd will
+typically map both stdin and stdout to the network socket, and may map
+stderr to the same socket or to some kind of logging mechanism. This allows
+the child program to be implemented with no knowledge of the network: it
+merely speaks its protocol by doing reads on fd0 and writes on fd1.</p>
+
+<p>Feeding into a parent's read pipe is used to gather output from the
+child, and is by far the most common way of interacting with child
+processes.</p>
+
+<p>Feeding from a parent's write pipe allows the parent to control the
+child. Programs like <q>bc</q> or <q>ftp</q> can be controlled this way, by
+writing commands into their stdin stream.</p>
+
+<p>The <q>childFDs</q> dictionary maps file descriptor numbers (as will be
+seen by the child process) to one of these three states. To map the fd to
+one of the parent's fds, simply provide the fd number as the value. To map
+it to a read pipe, use the string <q>r</q> as the value. To map it to a
+write pipe, use the string <q>w</q>.</p>
+
+<p>For example, the default mapping sets up the standard stdin/stdout/stderr
+pipes. It is implemented with the following dictionary:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">childFDs</span> = { <span class="py-src-number">0</span>: <span class="py-src-string">&quot;w&quot;</span>, <span class="py-src-number">1</span>: <span class="py-src-string">&quot;r&quot;</span>, <span class="py-src-number">2</span>: <span class="py-src-string">&quot;r&quot;</span> }
+</pre>
+
+<p>To launch a process which reads and writes to the same places that the
+parent python program does, use this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">childFDs</span> = { <span class="py-src-number">0</span>: <span class="py-src-number">0</span>, <span class="py-src-number">1</span>: <span class="py-src-number">1</span>, <span class="py-src-number">2</span>: <span class="py-src-number">2</span>}
+</pre>
+
+<p>To write into an additional fd (say it is fd number 4), use this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">childFDs</span> = { <span class="py-src-number">0</span>: <span class="py-src-string">&quot;w&quot;</span>, <span class="py-src-number">1</span>: <span class="py-src-string">&quot;r&quot;</span>, <span class="py-src-number">2</span>: <span class="py-src-string">&quot;r&quot;</span> , <span class="py-src-number">4</span>: <span class="py-src-string">&quot;w&quot;</span>}
+</pre>
+
+
+
+<h3>ProcessProtocols with extra file descriptors<a name="auto8"/></h3>
+
+<p>When you provide a <q>childFDs</q> dictionary with more than the normal
+three fds, you need addtional methods to access those pipes. These methods
+are more generalized than the <code>.outReceived</code> ones described above.
+In fact, those methods (<code>outReceived</code> and
+ <code>errReceived</code>) are actually just wrappers left in for
+compatibility with older code, written before this generalized fd mapping was
+implemented. The new list of things that can happen to your ProcessProtocol
+is as follows:</p>
+
+<ul>
+
+ <li><code>.connectionMade</code>: This is called when the program is
+ started.</li>
+
+ <li><code>.childDataReceived(childFD, data)</code>: This is called with
+ data that was received from one of the process' output pipes (i.e. where
+ the childFDs value was <q>r</q>. The actual file number (from the point of
+ view of the child process) is in <q>childFD</q>. For compatibility, the
+ default implementation of <code>.childDataReceived</code> dispatches to
+ <code>.outReceived</code> or <code>.errReceived</code> when <q>childFD</q>
+ is 1 or 2.</li>
+
+ <li><code>.childConnectionLost(childFD)</code>: This is called when the
+ reactor notices that one of the process' pipes has been closed. This
+ either means you have just closed down the parent's end of the pipe (with
+ <code>.transport.closeChildFD</code>), the child closed the pipe
+ explicitly (sometimes to indicate EOF), or the child process has
+ terminated and the kernel has closed all of its pipes. The <q>childFD</q>
+ argument tells you which pipe was closed. Note that you can only find out
+ about file descriptors which were mapped to pipes: when they are mapped to
+ existing fds the parent has no way to notice when they've been closed. For
+ compatibility, the default implementation dispatches to
+ <code>.inConnectionLost</code>, <code>.outConnectionLost</code>, or
+ <code>.errConnectionLost</code>.</li>
+
+ <li><code>.processEnded(status)</code>: This is called when the child
+ process has been reaped, and all pipes have been closed. This insures that
+ all data written by the child prior to its death will be received before
+ <code>.processEnded</code> is invoked.</li>
+
+</ul>
+
+
+<p>In addition to those methods, there are other methods available to
+influence the child process:</p>
+
+<ul>
+
+ <li><code>self.transport.writeToChild(childFD, data)</code>: Stuff some
+ data into an input pipe. <code>.write</code> simply writes to
+ childFD=0.</li>
+
+ <li><code>self.transport.closeChildFD(childFD)</code>: Close one of the
+ child's pipes. Closing an input pipe is a common way to indicate EOF to
+ the child process. Closing an output pipe is neither very friendly nor
+ very useful.</li>
+</ul>
+
+<h3>Examples<a name="auto9"/></h3>
+
+<p>GnuPG, the encryption program, can use additional file descriptors to
+accept a passphrase and emit status output. These are distinct from stdin
+(used to accept the crypttext), stdout (used to emit the plaintext), and
+stderr (used to emit human-readable status/warning messages). The passphrase
+FD reads until the pipe is closed and uses the resulting string to unlock
+the secret key that performs the actual decryption. The status FD emits
+machine-parseable status messages to indicate the validity of the signature,
+which key the message was encrypted to, etc.</p>
+
+<p>gpg accepts command-line arguments to specify what these fds are, and
+then assumes that they have been opened by the parent before the gpg process
+is started. It simply performs reads and writes to these fd numbers.</p>
+
+<p>To invoke gpg in decryption/verification mode, you would do something
+like the following:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">GPGProtocol</span>(<span class="py-src-parameter">ProcessProtocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">crypttext</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">crypttext</span> = <span class="py-src-variable">crypttext</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">plaintext</span> = <span class="py-src-string">&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">status</span> = <span class="py-src-string">&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">writeToChild</span>(<span class="py-src-number">3</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">passphrase</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">closeChildFD</span>(<span class="py-src-number">3</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">writeToChild</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">crypttext</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">closeChildFD</span>(<span class="py-src-number">0</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">childDataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">childFD</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">childFD</span> == <span class="py-src-number">1</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">plaintext</span> += <span class="py-src-variable">data</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">childFD</span> == <span class="py-src-number">4</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">status</span> += <span class="py-src-variable">data</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">processEnded</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">rc</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">value</span>.<span class="py-src-variable">exitCode</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">rc</span> == <span class="py-src-number">0</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">deferred</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">deferred</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">rc</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">decrypt</span>(<span class="py-src-parameter">crypttext</span>):
+ <span class="py-src-variable">gp</span> = <span class="py-src-variable">GPGProtocol</span>(<span class="py-src-variable">crypttext</span>)
+ <span class="py-src-variable">gp</span>.<span class="py-src-variable">deferred</span> = <span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">cmd</span> = [<span class="py-src-string">&quot;gpg&quot;</span>, <span class="py-src-string">&quot;--decrypt&quot;</span>, <span class="py-src-string">&quot;--passphrase-fd&quot;</span>, <span class="py-src-string">&quot;3&quot;</span>, <span class="py-src-string">&quot;--status-fd&quot;</span>, <span class="py-src-string">&quot;4&quot;</span>,
+ <span class="py-src-string">&quot;--batch&quot;</span>]
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">spawnProcess</span>(<span class="py-src-variable">gp</span>, <span class="py-src-variable">cmd</span>[<span class="py-src-number">0</span>], <span class="py-src-variable">cmd</span>, <span class="py-src-variable">env</span>=<span class="py-src-variable">None</span>,
+ <span class="py-src-variable">childFDs</span>={<span class="py-src-number">0</span>:<span class="py-src-string">&quot;w&quot;</span>, <span class="py-src-number">1</span>:<span class="py-src-string">&quot;r&quot;</span>, <span class="py-src-number">2</span>:<span class="py-src-number">2</span>, <span class="py-src-number">3</span>:<span class="py-src-string">&quot;w&quot;</span>, <span class="py-src-number">4</span>:<span class="py-src-string">&quot;r&quot;</span>})
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">gp</span>.<span class="py-src-variable">deferred</span>
+</pre>
+
+<p>In this example, the status output could be parsed after the fact. It
+could, of course, be parsed on the fly, as it is a simple line-oriented
+protocol. Methods from LineReceiver could be mixed in to make this parsing
+more convenient.</p>
+
+<p>The stderr mapping (<q>2:2</q>) used will cause any GPG errors to be
+emitted by the parent program, just as if those errors had caused in the
+parent itself. This is sometimes desireable (it roughly corresponds to
+letting exceptions propagate upwards), especially if you do not expect to
+encounter errors in the child process and want them to be more visible to
+the end user. The alternative is to map stderr to a read-pipe and handle any
+such output from within the ProcessProtocol (roughly corresponding to
+catching the exception locally).</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/producers.html b/doc/core/howto/producers.html
new file mode 100644
index 0000000..764ab88
--- /dev/null
+++ b/doc/core/howto/producers.html
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Producers and Consumers: Efficient High-Volume Streaming</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Producers and Consumers: Efficient High-Volume Streaming</h1>
+ <div class="toc"><ol><li><a href="#auto0">Push Producers</a></li><ul><li><a href="#auto1">pauseProducing()</a></li><li><a href="#auto2">resumeProducing()</a></li><li><a href="#auto3">stopProducing()</a></li></ul><li><a href="#auto4">Pull Producers</a></li><ul><li><a href="#auto5">resumeProducing()</a></li><li><a href="#auto6">stopProducing()</a></li></ul><li><a href="#auto7">Consumers</a></li><ul><li><a href="#auto8">registerProducer(producer, streaming)</a></li><li><a href="#auto9">unregisterProducer()</a></li><li><a href="#auto10">write(data)</a></li><li><a href="#auto11">finish()</a></li></ul><li><a href="#auto12">Further Reading</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <p>The purpose of this guide is to describe the Twisted <em>producer</em> and <em>consumer</em> system. The producer system allows applications to stream large amounts of data in a manner which is both memory and CPU efficient, and which does not introduce a source of unacceptable latency into the reactor.</p>
+
+ <p>Readers should have at least a passing familiarity with the terminology associated with interfaces.</p>
+
+ <h2>Push Producers<a name="auto0"/></h2>
+
+ <p>A push producer is one which will continue to generate data without external prompting until told to stop; a pull producer will generate one chunk of data at a time in response to an explicit request for more data.</p>
+
+ <p>The push producer API is defined by the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IPushProducer.html" title="twisted.internet.interfaces.IPushProducer">IPushProducer</a></code> interface. It is best to create a push producer when data generation is closedly tied to an event source. For example, a proxy which forwards incoming bytes from one socket to another outgoing socket might be implemented using a push producer: the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProtocol.dataReceived.html" title="twisted.internet.interfaces.IProtocol.dataReceived">dataReceived</a></code> takes the role of an event source from which the producer generates bytes, and requires no external intervention in order to do so.</p>
+
+ <p>There are three methods which may be invoked on a push producer at various points in its lifetime: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IPushProducer.pauseProducing.html" title="twisted.internet.interfaces.IPushProducer.pauseProducing">pauseProducing</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IPushProducer.resumeProducing.html" title="twisted.internet.interfaces.IPushProducer.resumeProducing">resumeProducing</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProducer.stopProducing.html" title="twisted.internet.interfaces.IProducer.stopProducing">stopProducing</a></code>.</p>
+
+ <h3>pauseProducing()<a name="auto1"/></h3>
+
+ <p>In order to avoid the possibility of using an unbounded amount of memory to buffer produced data which cannot be processed quickly enough, it is necessary to be able to tell a push producer to stop producing data for a while. This is done using the <code class="python">pauseProducing</code> method. Implementers of a push producer should temporarily stop producing data when this method is invoked.</p>
+
+ <h3>resumeProducing()<a name="auto2"/></h3>
+
+ <p>After a push producer has been paused for some time, the excess of data which it produced will have been processed and the producer may again begin producing data. When the time for this comes, the push producer will have <code class="python">resumeProducing</code> invoked on it.</p>
+
+ <h3>stopProducing()<a name="auto3"/></h3>
+
+ <p>Most producers will generate some finite (albeit, perhaps, unknown in advance) amount of data and then stop, having served their intended purpose. However, it is possible that before this happens an event will occur which renders the remaining, unproduced data irrelevant. In these cases, producing it anyway would be wasteful. The <code class="python">stopProducing</code> method will be invoked on the push producer. The implementation should stop producing data and clean up any resources owned by the producer.</p>
+
+ <h2>Pull Producers<a name="auto4"/></h2>
+
+ <p>The pull producer API is defined by the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IPullProducer.html" title="twisted.internet.interfaces.IPullProducer">IPullProducer</a></code> interface. Pull producers are useful in cases where there is no clear event source involved with the generation of data. For example, if the data is the result of some algorithmic process that is bound only by CPU time, a pull producer is appropriate.</p>
+
+ <p>Pull producers are defined in terms of only two methods: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IPullProducer.resumeProducing.html" title="twisted.internet.interfaces.IPullProducer.resumeProducing">resumeProducing</a></code> and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProducer.stopProducing.html" title="twisted.internet.interfaces.IProducer.stopProducing">stopProducing</a></code>.</p>
+
+ <h3>resumeProducing()<a name="auto5"/></h3>
+
+ <p>Unlike push producers, a pull producer is expected to <strong>only</strong> produce data in response to <code class="python">resumeProducing</code> being called. This method will be called whenever more data is required. How much data to produce in response to this method call depends on various factors: too little data and runtime costs will be dominated by the back-and-forth event notification associated with a buffer becoming empty and requesting more data to process; too much data and memory usage will be driven higher than it needs to be and the latency associated with creating so much data will cause overall performance in the application to suffer. A good rule of thumb is to generate between 16 and 64 kilobytes of data at a time, but you should experiment with various values to determine what is best for your application.</p>
+
+ <h3>stopProducing()<a name="auto6"/></h3>
+
+ <p>This method has the same meaning for pull producers as it does for push producers.</p>
+
+ <h2>Consumers<a name="auto7"/></h2>
+
+ <p>This far, I've discussed the various external APIs of the two kinds of producers supported by Twisted. However, I have not mentioned where the data a producer generates actually goes, nor what entity is responsible for invoking these APIs. Both of these roles are filled by <em>consumers</em>. Consumers are defined by the two interfaces <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IConsumer.html" title="twisted.internet.interfaces.IConsumer">IConsumer</a></code> and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IFinishableConsumer.html" title="twisted.internet.interfaces.IFinishableConsumer">IFinishableConsumer</a></code>.</p>
+
+ <p>The slightly simpler of these two interfaces, <code class="python">IConsumer</code>, defines three methods: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IConsumer.registerProducer.html" title="twisted.internet.interfaces.IConsumer.registerProducer">registerProducer</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IConsumer.unregisterProducer.html" title="twisted.internet.interfaces.IConsumer.unregisterProducer">unregisterProducer</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IConsumer.write.html" title="twisted.internet.interfaces.IConsumer.write">write</a></code>. <code class="python">IFinishableConsumer</code> adds <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IFinishableConsumer.finish.html" title="twisted.internet.interfaces.IFinishableConsumer.finish">finish</a></code>.</p>
+
+ <h3>registerProducer(producer, streaming)<a name="auto8"/></h3>
+
+ <p>So that a consumer can invoke methods on a producer, the consumer needs to be told about the producer. This is done with the <code class="python">registerProducer</code> method. The first argument is either a <code class="python">IPullProducer</code> or <code class="python">IPushProducer</code> provider; the second argument indicates which of these interfaces is provided: <code class="python">True</code> for push producers, <code class="python">False</code> for pull producers.</p>
+
+ <h3>unregisterProducer()<a name="auto9"/></h3>
+
+ <p>Eventually a consumer will not longer be interested in a producer. This could be because the producer has finished generating all its data, or because the consumer is moving on to something else, or any number of other reasons. In any case, this method reverses the effects of <code class="python">registerProducer</code>.</p>
+
+ <h3>write(data)<a name="auto10"/></h3>
+
+ <p>As you might guess, this is the method which a producer calls when it has generated some data. Push producers should call it as frequently as they like as long as they are not paused. Pull producers should call it once for each time <code class="python">resumeProducing</code> is called on them.</p>
+
+ <h3>finish()<a name="auto11"/></h3>
+
+ <p>This method of <code class="python">IFinishableConsumer</code>s gives producers a way to explicitly notify the consumer that they have generated all the data they will ever generate.</p>
+
+ <h2>Further Reading<a name="auto12"/></h2>
+
+ <p>An example push producer application can be found in <code class="py-filename">doc/examples/streaming.py</code>.</p>
+
+ <ul>
+
+ <li><a href="components.html" shape="rect">Components: Interfaces and Adapters</a></li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.protocols.basic.FileSender.html" title="twisted.protocols.basic.FileSender">FileSender</a></code>: A Simple Pull Producer</li>
+
+ </ul>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/quotes.html b/doc/core/howto/quotes.html
new file mode 100644
index 0000000..e72936c
--- /dev/null
+++ b/doc/core/howto/quotes.html
@@ -0,0 +1,214 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Setting up the TwistedQuotes application</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Setting up the TwistedQuotes application</h1>
+ <div class="toc"><ol><li><a href="#auto0">Goal</a></li><li><a href="#auto1">Setting up the TwistedQuotes project directory</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Goal<a name="auto0"/></h2>
+
+<p>This document describes how to set up the TwistedQuotes application used in
+a number of other documents, such as <a href="design.html" shape="rect">designing Twisted applications</a>.</p>
+
+<h2>Setting up the TwistedQuotes project directory<a name="auto1"/></h2>
+
+<p>In order to run the Twisted Quotes example, you will need to do the
+following:</p>
+
+<ol>
+<li>Make a <code>TwistedQuotes</code> directory on your system</li>
+<li>Place the following files in the <code>TwistedQuotes</code> directory:
+ <ul>
+ <li><div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-string">&quot;&quot;&quot;
+Twisted Quotes
+&quot;&quot;&quot;</span>
+</pre><div class="caption">Source listing - <a href="listings/TwistedQuotes/__init__.py"><span class="filename">listings/TwistedQuotes/__init__.py</span></a></div></div> (this
+ file marks it as a package, see <a href="http://docs.python.org/tutorial/modules.html#packages" shape="rect">this section</a> of the Python tutorial for more on packages)</li>
+ <li><div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">random</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">choice</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">TwistedQuotes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">quoteproto</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">StaticQuoter</span>:
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a static quote.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">quoteproto</span>.<span class="py-src-variable">IQuoter</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">quote</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">quote</span> = <span class="py-src-variable">quote</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">quote</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FortuneQuoter</span>:
+ <span class="py-src-string">&quot;&quot;&quot;
+ Load quotes from a fortune-format file.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">quoteproto</span>.<span class="py-src-variable">IQuoter</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filenames</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filenames</span> = <span class="py-src-variable">filenames</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">quoteFile</span> = <span class="py-src-variable">file</span>(<span class="py-src-variable">choice</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filenames</span>))
+ <span class="py-src-variable">quotes</span> = <span class="py-src-variable">quoteFile</span>.<span class="py-src-variable">read</span>().<span class="py-src-variable">split</span>(<span class="py-src-string">'\n%\n'</span>)
+ <span class="py-src-variable">quoteFile</span>.<span class="py-src-variable">close</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">choice</span>(<span class="py-src-variable">quotes</span>)
+</pre><div class="caption">Source listing - <a href="listings/TwistedQuotes/quoters.py"><span class="filename">listings/TwistedQuotes/quoters.py</span></a></div></div></li>
+ <li><div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IQuoter</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ An object that returns quotes.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a quote.
+ &quot;&quot;&quot;</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">quoter</span>.<span class="py-src-variable">getQuote</span>()+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTDFactory</span>(<span class="py-src-parameter">Factory</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ A factory for the Quote of the Day protocol.
+
+ @type quoter: L{IQuoter} provider
+ @ivar quoter: An object which provides L{IQuoter} which will be used by
+ the L{QOTD} protocol to get quotes to emit.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">QOTD</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">quoter</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">quoter</span> = <span class="py-src-variable">quoter</span>
+</pre><div class="caption">Source listing - <a href="listings/TwistedQuotes/quoteproto.py"><span class="filename">listings/TwistedQuotes/quoteproto.py</span></a></div></div></li>
+ </ul>
+</li>
+<li>Add the <code>TwistedQuotes</code> directory's <em>parent</em> to your Python
+path. For example, if the TwistedQuotes directory's path is
+ <code>/mystuff/TwistedQuotes</code> or <code>c:\mystuff\TwistedQuotes</code>
+add <code>/mystuff</code> to your Python path. On UNIX this would be <code class="shell">export PYTHONPATH=/mystuff:$PYTHONPATH</code>, on Microsoft
+Windows change the <code class="shell">PYTHONPATH</code> variable through the
+Systems Properties dialog by adding <code class="shell">;c:\mystuff</code> at the
+end.</li>
+<li>
+Test your package by trying to import it in the Python interpreter:
+<pre class="python-interpreter" xml:space="preserve">
+Python 2.1.3 (#1, Apr 20 2002, 22:45:31)
+[GCC 2.95.4 20011002 (Debian prerelease)] on linux2
+Type &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
+&gt;&gt;&gt; import TwistedQuotes
+&gt;&gt;&gt; # No traceback means you're fine.
+</pre>
+</li>
+</ol>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/rdbms.html b/doc/core/howto/rdbms.html
new file mode 100644
index 0000000..b1c053d
--- /dev/null
+++ b/doc/core/howto/rdbms.html
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: twisted.enterprise.adbapi: Twisted RDBMS support</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">twisted.enterprise.adbapi: Twisted RDBMS support</h1>
+ <div class="toc"><ol><li><a href="#auto0">Abstract</a></li><li><a href="#auto1">What you should already know</a></li><li><a href="#auto2">Quick Overview</a></li><li><a href="#auto3">How do I use adbapi?</a></li><li><a href="#auto4">Examples of various database adapters</a></li><li><a href="#auto5">And that's it!</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Abstract<a name="auto0"/></h2>
+
+ <p>Twisted is an asynchronous networking framework, but most
+ database API implementations unfortunately have blocking
+ interfaces -- for this reason, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.enterprise.adbapi.html" title="twisted.enterprise.adbapi">twisted.enterprise.adbapi</a></code> was created. It is
+ a non-blocking interface to the standardized DB-API 2.0 API,
+ which allows you to access a number of different RDBMSes.</p>
+
+ <h2>What you should already know<a name="auto1"/></h2>
+
+ <ul>
+ <li>Python :-)</li>
+
+ <li>How to write a simple Twisted Server (see <a href="servers.html" shape="rect">this tutorial</a> to learn how)</li>
+
+ <li>Familiarity with using database interfaces (see <a href="http://www.python.org/dev/peps/pep-0249/" shape="rect">
+ the documentation for DBAPI 2.0</a> or this <a href="http://www.amk.ca/python/writing/DB-API.html" shape="rect">article</a>
+ by Andrew Kuchling)</li>
+ </ul>
+
+ <h2>Quick Overview<a name="auto2"/></h2>
+
+ <p>Twisted is an asynchronous framework. This means standard
+ database modules cannot be used directly, as they typically
+ work something like:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-comment"># Create connection... </span>
+<span class="py-src-variable">db</span> = <span class="py-src-variable">dbmodule</span>.<span class="py-src-variable">connect</span>(<span class="py-src-string">'mydb'</span>, <span class="py-src-string">'andrew'</span>, <span class="py-src-string">'password'</span>)
+<span class="py-src-comment"># ...which blocks for an unknown amount of time </span>
+
+<span class="py-src-comment"># Create a cursor </span>
+<span class="py-src-variable">cursor</span> = <span class="py-src-variable">db</span>.<span class="py-src-variable">cursor</span>()
+
+<span class="py-src-comment"># Do a query... </span>
+<span class="py-src-variable">resultset</span> = <span class="py-src-variable">cursor</span>.<span class="py-src-variable">query</span>(<span class="py-src-string">'SELECT * FROM table WHERE ...'</span>)
+<span class="py-src-comment"># ...which could take a long time, perhaps even minutes.</span>
+</pre>
+
+ <p>Those delays are unacceptable when using an asynchronous
+ framework such as Twisted. For this reason, twisted provides
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.enterprise.adbapi.html" title="twisted.enterprise.adbapi">twisted.enterprise.adbapi</a></code>, an
+ asynchronous wrapper for any <a href="http://www.python.org/dev/peps/pep-0249/" shape="rect">
+ DB-API 2.0</a>-compliant module.</p>
+
+ <p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.enterprise.adbapi.html" title="twisted.enterprise.adbapi">enterprise.adbapi</a></code> will do
+ blocking
+ database operations in separate threads, which trigger
+ callbacks in the originating thread when they complete. In the
+ meantime, the original thread can continue doing normal work,
+ like servicing other requests.</p>
+
+ <h2>How do I use adbapi?<a name="auto3"/></h2>
+
+ <p>Rather than creating a database connection directly, use the
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.enterprise.adbapi.ConnectionPool.html" title="twisted.enterprise.adbapi.ConnectionPool">adbapi.ConnectionPool</a></code>
+ class to manage
+ a connections for you. This allows <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.enterprise.adbapi.html" title="twisted.enterprise.adbapi">enterprise.adbapi</a></code> to use multiple
+ connections, one per thread. This is easy:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-comment"># Using the &quot;dbmodule&quot; from the previous example, create a ConnectionPool </span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">enterprise</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">adbapi</span>
+<span class="py-src-variable">dbpool</span> = <span class="py-src-variable">adbapi</span>.<span class="py-src-variable">ConnectionPool</span>(<span class="py-src-string">&quot;dbmodule&quot;</span>, <span class="py-src-string">'mydb'</span>, <span class="py-src-string">'andrew'</span>, <span class="py-src-string">'password'</span>)
+</pre>
+
+ <p>Things to note about doing this:</p>
+
+ <ul>
+ <li>There is no need to import dbmodule directly. You just
+ pass the name to <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.enterprise.adbapi.ConnectionPool.html" title="twisted.enterprise.adbapi.ConnectionPool">adbapi.ConnectionPool</a></code>'s constructor.</li>
+
+ <li>The parameters you would pass to dbmodule.connect are
+ passed as extra arguments to <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.enterprise.adbapi.ConnectionPool.html" title="twisted.enterprise.adbapi.ConnectionPool">adbapi.ConnectionPool</a></code>'s constructor.
+ Keyword parameters work as well.</li>
+ </ul>
+
+ <p>Now we can do a database query:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-comment"># equivalent of cursor.execute(statement), return cursor.fetchall():</span>
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getAge</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">dbpool</span>.<span class="py-src-variable">runQuery</span>(<span class="py-src-string">&quot;SELECT age FROM users WHERE name = ?&quot;</span>, <span class="py-src-variable">user</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">l</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">l</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">l</span>[<span class="py-src-number">0</span>][<span class="py-src-number">0</span>], <span class="py-src-string">&quot;years old&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;No such user&quot;</span>
+
+<span class="py-src-variable">getAge</span>(<span class="py-src-string">&quot;joe&quot;</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+</pre>
+
+ <p>This is straightforward, except perhaps for the return value
+ of <code>getAge</code>. It returns a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code>, which allows
+ arbitrary callbacks to be called upon completion (or upon
+ failure). More documentation on Deferred is available <a href="defer.html" shape="rect">here</a>.</p>
+
+ <p>In addition to <code>runQuery</code>, there is also <code>runOperation</code>,
+ and <code>runInteraction</code> that gets called with a callable (e.g. a function).
+ The function will be called in the thread with a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.enterprise.adbapi.Transaction.html" title="twisted.enterprise.adbapi.Transaction">twisted.enterprise.adbapi.Transaction</a></code>,
+ which basically mimics a DB-API cursor. In all cases a database transaction will be
+ commited after your database usage is finished, unless an exception is raised in
+ which case it will be rolled back.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">_getAge</span>(<span class="py-src-parameter">txn</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-comment"># this will run in a thread, we can use blocking calls</span>
+ <span class="py-src-variable">txn</span>.<span class="py-src-variable">execute</span>(<span class="py-src-string">&quot;SELECT * FROM foo&quot;</span>)
+ <span class="py-src-comment"># ... other cursor commands called on txn ...</span>
+ <span class="py-src-variable">txn</span>.<span class="py-src-variable">execute</span>(<span class="py-src-string">&quot;SELECT age FROM users WHERE name = ?&quot;</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">txn</span>.<span class="py-src-variable">fetchall</span>()
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">result</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">result</span>[<span class="py-src-number">0</span>][<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getAge</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">dbpool</span>.<span class="py-src-variable">runInteraction</span>(<span class="py-src-variable">_getAge</span>, <span class="py-src-variable">user</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">age</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">age</span> != <span class="py-src-variable">None</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">age</span>, <span class="py-src-string">&quot;years old&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;No such user&quot;</span>
+
+<span class="py-src-variable">getAge</span>(<span class="py-src-string">&quot;joe&quot;</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+</pre>
+
+ <p>Also worth noting is that these examples assumes that dbmodule
+ uses the <q>qmarks</q> paramstyle (see the DB-API specification). If
+ your dbmodule uses a different paramstyle (e.g. pyformat) then
+ use that. Twisted doesn't attempt to offer any sort of magic
+ paramater munging -- <code class="python">runQuery(query,
+ params, ...)</code> maps directly onto <code class="python">cursor.execute(query, params, ...)</code>.</p>
+
+ <h2>Examples of various database adapters<a name="auto4"/></h2>
+
+ <p>Notice that the first argument is the module name you would
+ usually import and get <code class="python">connect(...)</code>
+ from, and that following arguments are whatever arguments you'd
+ call <code class="python">connect(...)</code> with.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">enterprise</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">adbapi</span>
+
+<span class="py-src-comment"># Gadfly</span>
+<span class="py-src-variable">cp</span> = <span class="py-src-variable">adbapi</span>.<span class="py-src-variable">ConnectionPool</span>(<span class="py-src-string">&quot;gadfly&quot;</span>, <span class="py-src-string">&quot;test&quot;</span>, <span class="py-src-string">&quot;/tmp/gadflyDB&quot;</span>)
+
+<span class="py-src-comment"># PostgreSQL PyPgSQL</span>
+<span class="py-src-variable">cp</span> = <span class="py-src-variable">adbapi</span>.<span class="py-src-variable">ConnectionPool</span>(<span class="py-src-string">&quot;pyPgSQL.PgSQL&quot;</span>, <span class="py-src-variable">database</span>=<span class="py-src-string">&quot;test&quot;</span>)
+
+<span class="py-src-comment"># MySQL</span>
+<span class="py-src-variable">cp</span> = <span class="py-src-variable">adbapi</span>.<span class="py-src-variable">ConnectionPool</span>(<span class="py-src-string">&quot;MySQLdb&quot;</span>, <span class="py-src-variable">db</span>=<span class="py-src-string">&quot;test&quot;</span>)
+</pre>
+
+ <h2>And that's it!<a name="auto5"/></h2>
+
+ <p>That's all you need to know to use a database from within
+ Twisted. You probably should read the adbapi module's
+ documentation to get an idea of the other functions it has, but
+ hopefully this document presents the core ideas.</p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/reactor-basics.html b/doc/core/howto/reactor-basics.html
new file mode 100644
index 0000000..a750f78
--- /dev/null
+++ b/doc/core/howto/reactor-basics.html
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Reactor Overview</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Reactor Overview</h1>
+ <div class="toc"><ol><li><a href="#auto0">Reactor Basics</a></li><li><a href="#auto1">Using the reactor object</a></li></ol></div>
+ <div class="content">
+
+ <span/>
+
+ <p>
+ This HOWTO introduces the Twisted reactor, describes the basics of the
+ reactor and links to the various reactor interfaces.
+ </p>
+
+ <h2>Reactor Basics<a name="auto0"/></h2>
+
+ <p>The reactor is the core of the event loop within Twisted -- the loop
+ which drives applications using Twisted. The event loop is a programming
+ construct that waits for and dispatches events or messages in a program.
+ It works by calling some internal or external &quot;event provider&quot;, which
+ generally blocks until an event has arrived, and then calls the relevant
+ event handler (&quot;dispatches the event&quot;). The reactor provides basic
+ interfaces to a number of services, including network communications,
+ threading, and event dispatching.
+ </p>
+
+ <p>
+ For information about using the reactor and the Twisted event loop, see:
+ </p>
+
+ <ul>
+ <li>the event dispatching howtos: <a href="time.html" shape="rect">Scheduling</a> and <a href="defer.html" shape="rect">Using Deferreds</a>;</li>
+ <li>the communication howtos: <a href="servers.html" shape="rect">TCP
+ servers</a>, <a href="clients.html" shape="rect">TCP clients</a>, <a href="udp.html" shape="rect">UDP networking</a> and <a href="process.html" shape="rect">Using
+ processes</a>; and</li>
+ <li><a href="threading.html" shape="rect">Using threads</a>.</li>
+ </ul>
+
+ <p>There are multiple implementations of the reactor, each
+ modified to provide better support for specialized features
+ over the default implementation. More information about these
+ and how to use a particular implementation is available via
+ <a href="choosing-reactor.html" shape="rect">Choosing a Reactor</a>.</p>
+
+
+ <p>
+ Twisted applications can use the interfaces in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.html" title="twisted.application.service">twisted.application.service</a></code> to configure and run the
+ application instead of using
+ boilerplate reactor code. See <a href="application.html" shape="rect">Using Application</a> for an introduction to
+ Application.
+ </p>
+
+ <h2>Using the reactor object<a name="auto1"/></h2>
+
+ <p>You can get to the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code> object using the following code:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <p>The reactor usually implements a set of interfaces, but
+ depending on the chosen reactor and the platform, some of
+ the interfaces may not be implemented:</p>
+
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorCore.html" title="twisted.internet.interfaces.IReactorCore">IReactorCore</a></code>: Core (required) functionality.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorFDSet.html" title="twisted.internet.interfaces.IReactorFDSet">IReactorFDSet</a></code>: Use FileDescriptor objects.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorProcess.html" title="twisted.internet.interfaces.IReactorProcess">IReactorProcess</a></code>: Process management. Read the
+ <a href="process.html" shape="rect">Using Processes</a> document for
+ more information.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorSSL.html" title="twisted.internet.interfaces.IReactorSSL">IReactorSSL</a></code>: SSL networking support.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTCP.html" title="twisted.internet.interfaces.IReactorTCP">IReactorTCP</a></code>: TCP networking support. More information
+ can be found in the <a href="servers.html" shape="rect">Writing Servers</a>
+ and <a href="clients.html" shape="rect">Writing Clients</a> documents.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorThreads.html" title="twisted.internet.interfaces.IReactorThreads">IReactorThreads</a></code>: Threading use and management. More
+ information can be found within <a href="threading.html" shape="rect">Threading In Twisted</a>.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTime.html" title="twisted.internet.interfaces.IReactorTime">IReactorTime</a></code>: Scheduling interface. More information
+ can be found within <a href="time.html" shape="rect">Scheduling Tasks</a>.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUDP.html" title="twisted.internet.interfaces.IReactorUDP">IReactorUDP</a></code>: UDP networking support. More information
+ can be found within <a href="udp.html" shape="rect">UDP Networking</a>.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUNIX.html" title="twisted.internet.interfaces.IReactorUNIX">IReactorUNIX</a></code>: UNIX socket support.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorSocket.html" title="twisted.internet.interfaces.IReactorSocket">IReactorSocket</a></code>: Third-party socket support.</li>
+ </ul>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/sendmsg.html b/doc/core/howto/sendmsg.html
new file mode 100644
index 0000000..7570c26
--- /dev/null
+++ b/doc/core/howto/sendmsg.html
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Extremely Low-Level Socket Operations</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Extremely Low-Level Socket Operations</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><ul><li><a href="#auto1">sendmsg</a></li><li><a href="#auto2">recvmsg</a></li></ul><li><a href="#auto3">Sending And Receiving Regular Data</a></li><li><a href="#auto4">Copying File Descriptors</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Introduction<a name="auto0"/></h2>
+
+ <p>
+ Beyond supporting streams of data (SOCK_STREAM) or datagrams (SOCK_DGRAM),
+ POSIX sockets have additional features not accessible via send(2) and
+ recv(2). These features include things like scatter/gather I/O,
+ duplicating file descriptors into other processes, and accessing
+ out-of-band data.
+ </p>
+
+ <p>
+ Twisted includes a wrapper around the two C APIs which make these things
+ possible,
+ <a href="http://www.opengroup.org/onlinepubs/007908799/xns/sendmsg.html" shape="rect">sendmsg</a>
+ and
+ <a href="http://www.opengroup.org/onlinepubs/007908799/xns/recvmsg.html" shape="rect">recvmsg</a>.
+ This document covers their usage. It is intended for Twisted maintainers.
+ Application developers looking for this functionality should look for the
+ high-level APIs Twisted provides on top of these wrappers.
+ </p>
+
+ <h3>sendmsg<a name="auto1"/></h3>
+
+ <p>
+ <code>sendmsg(2)</code> exposes nearly all sender-side functionality of a
+ socket. For a SOCK_STREAM socket, it can send bytes that become part of
+ the stream of data being carried over the connection. For a SOCK_DGRAM
+ socket, it can send bytes that become datagrams sent from the socket. It
+ can send data from multiple memory locations (gather I/O). Over AF_UNIX
+ sockets, it can copy file descriptors into whichever process is receiving
+ on the other side. The wrapper included in Twisted,
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.sendmsg.send1msg.html" title="twisted.python.sendmsg.send1msg">send1msg</a></code>, exposes
+ many (but not all) of these features. This document covers the usage of
+ the features it does expose. The alternate spelling for the wrapper is
+ used to indicate the primary limitation, which is it that the interface
+ supports sending only one <em>iovec</em> at a time.
+ </p>
+
+ <h3>recvmsg<a name="auto2"/></h3>
+
+ <p>
+ Likewise, <code>recvmsg(2)</code> exposes nearly all the receiver-side
+ functionality of a socket. It can receive stream data over from a
+ SOCK_STREAM socket or datagrams from a SOCK_DGRAM socket. It can receive
+ that data into multiple memory locations (scatter I/O), and it can receive
+ those copied file descriptors. The wrapper included in
+ Twisted, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.sendmsg.recv1msg.html" title="twisted.python.sendmsg.recv1msg">recv1msg</a></code>,
+ exposes many (but not all) of these features. This document covers the
+ usage of the features it does expose. The alternate spelling for the
+ wrapper is used to indicate the primary limitation, which is that the
+ interface supports receiving only one <em>iovec</em> at a time.
+ </p>
+
+ <h2>Sending And Receiving Regular Data<a name="auto3"/></h2>
+
+ <p>
+ sendmsg can be used in a way which makes it equivalent to using the send
+ call. The first argument to sendmsg is (in this case and all others) a
+ file descriptor over which to send the data. The second argument is a
+ string giving the data to send.
+ </p>
+
+ <p>
+ On the other end, recvmsg can be used to replace a recv call. The first
+ argument to recvmsg is (again, in all cases) a file descriptor over which
+ to receive the data. The second argument is an integer giving the maximum
+ number of data to receive.
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Demonstration of sending bytes over a TCP connection using sendmsg.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">socket</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">socketpair</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">sendmsg</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">send1msg</span>, <span class="py-src-variable">recv1msg</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">foo</span>, <span class="py-src-variable">bar</span> = <span class="py-src-variable">socketpair</span>()
+ <span class="py-src-variable">sent</span> = <span class="py-src-variable">send1msg</span>(<span class="py-src-variable">foo</span>.<span class="py-src-variable">fileno</span>(), <span class="py-src-string">&quot;Hello, world&quot;</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Sent&quot;</span>, <span class="py-src-variable">sent</span>, <span class="py-src-string">&quot;bytes&quot;</span>
+ (<span class="py-src-variable">received</span>, <span class="py-src-variable">flags</span>, <span class="py-src-variable">ancillary</span>) = <span class="py-src-variable">recv1msg</span>(<span class="py-src-variable">bar</span>.<span class="py-src-variable">fileno</span>(), <span class="py-src-number">1024</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Received&quot;</span>, <span class="py-src-variable">repr</span>(<span class="py-src-variable">received</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Extra stuff, boring in this case&quot;</span>, <span class="py-src-variable">flags</span>, <span class="py-src-variable">ancillary</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption"> - <a href="listings/sendmsg/send_replacement.py"><span class="filename">listings/sendmsg/send_replacement.py</span></a></div></div>
+
+ <h2>Copying File Descriptors<a name="auto4"/></h2>
+
+ <p>
+ Used with an AF_UNIX socket, sendmsg send a copy of a file descriptor into
+ whatever process is receiving on the other end of the socket. This is
+ done using the ancillary data argument. Ancillary data consists of a list
+ of three-tuples. A three-tuple constructed with SOL_SOCKET, SCM_RIGHTS,
+ and a platform-endian packed file descriptor number will copy that file
+ descriptor.
+ </p>
+
+ <p>
+ File descriptors copied this way must be received using a recvmsg call.
+ No special arguments are required to receive these descriptors. They will
+ appear, encoded as a native-order string, in the ancillary data list
+ returned by recvmsg.
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+</p><span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Demonstration of copying a file descriptor over an AF_UNIX connection using
+sendmsg.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">os</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pipe</span>, <span class="py-src-variable">read</span>, <span class="py-src-variable">write</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">socket</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SOL_SOCKET</span>, <span class="py-src-variable">socketpair</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">struct</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unpack</span>, <span class="py-src-variable">pack</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">sendmsg</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SCM_RIGHTS</span>, <span class="py-src-variable">send1msg</span>, <span class="py-src-variable">recv1msg</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">foo</span>, <span class="py-src-variable">bar</span> = <span class="py-src-variable">socketpair</span>()
+ <span class="py-src-variable">reader</span>, <span class="py-src-variable">writer</span> = <span class="py-src-variable">pipe</span>()
+
+ <span class="py-src-comment"># Send a copy of the descriptor. Notice that there must be at least one</span>
+ <span class="py-src-comment"># byte of normal data passed in.</span>
+ <span class="py-src-variable">sent</span> = <span class="py-src-variable">send1msg</span>(
+ <span class="py-src-variable">foo</span>.<span class="py-src-variable">fileno</span>(), <span class="py-src-string">&quot;\x00&quot;</span>, <span class="py-src-number">0</span>,
+ [(<span class="py-src-variable">SOL_SOCKET</span>, <span class="py-src-variable">SCM_RIGHTS</span>, <span class="py-src-variable">pack</span>(<span class="py-src-string">&quot;i&quot;</span>, <span class="py-src-variable">reader</span>))])
+
+ <span class="py-src-comment"># Receive the copy, including that one byte of normal data.</span>
+ <span class="py-src-variable">data</span>, <span class="py-src-variable">flags</span>, <span class="py-src-variable">ancillary</span> = <span class="py-src-variable">recv1msg</span>(<span class="py-src-variable">bar</span>.<span class="py-src-variable">fileno</span>(), <span class="py-src-number">1024</span>)
+ <span class="py-src-variable">duplicate</span> = <span class="py-src-variable">unpack</span>(<span class="py-src-string">&quot;i&quot;</span>, <span class="py-src-variable">ancillary</span>[<span class="py-src-number">0</span>][<span class="py-src-number">2</span>])[<span class="py-src-number">0</span>]
+
+ <span class="py-src-comment"># Demonstrate that the copy works just like the original</span>
+ <span class="py-src-variable">write</span>(<span class="py-src-variable">writer</span>, <span class="py-src-string">&quot;Hello, world&quot;</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Read from original (%d): %r&quot;</span> % (<span class="py-src-variable">reader</span>, <span class="py-src-variable">read</span>(<span class="py-src-variable">reader</span>, <span class="py-src-number">6</span>))
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Read from duplicate (%d): %r&quot;</span> % (<span class="py-src-variable">duplicate</span>, <span class="py-src-variable">read</span>(<span class="py-src-variable">duplicate</span>, <span class="py-src-number">6</span>))
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption"> - <a href="listings/sendmsg/copy_descriptor.py"><span class="filename">listings/sendmsg/copy_descriptor.py</span></a></div></div>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/servers.html b/doc/core/howto/servers.html
new file mode 100644
index 0000000..232a574
--- /dev/null
+++ b/doc/core/howto/servers.html
@@ -0,0 +1,548 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Writing Servers</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Writing Servers</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Protocols</a></li><ul><li><a href="#auto2">loseConnection() and abortConnection()</a></li><li><a href="#auto3">Using the Protocol</a></li><li><a href="#auto4">Helper Protocols</a></li><li><a href="#auto5">State Machines</a></li></ul><li><a href="#auto6">Factories</a></li><ul><li><a href="#auto7">Simpler Protocol Creation</a></li><li><a href="#auto8">Factory Startup and Shutdown</a></li></ul><li><a href="#auto9">Putting it All Together</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>This document explains how you can use Twisted to implement
+ network protocol parsing and handling for TCP servers (the same
+ code can be reused for SSL and Unix socket servers). There is
+ a <a href="udp.html" shape="rect">separate document</a> covering UDP.</p>
+
+ <p>Your protocol handling class will usually subclass <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">twisted.internet.protocol.Protocol</a></code>. Most
+ protocol handlers inherit either from this class or from one of
+ its convenience children. An instance of the protocol class
+ is instantiated per-connection, on demand, and will go
+ away when the connection is finished. This means that
+ persistent configuration is not saved in the
+ <code>Protocol</code>.</p>
+
+ <p>The persistent configuration is kept in a <code>Factory</code>
+ class, which usually inherits
+ from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">twisted.internet.protocol.Factory</a></code>. The <code>buildProtocol</code>
+ method of the <code>Factory</code> is used to create
+ a <code>Protocol</code> for each new connection.</p>
+
+ <p>It is usually useful to be able to offer the same service on
+ multiple ports or network addresses. This is why
+ the <code>Factory</code> does not listen to connections, and in
+ fact does not know anything about the
+ network. See <a href="endpoints.html" shape="rect">the endpoints
+ documentation</a> for more information,
+ or <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTCP.listenTCP.html" title="twisted.internet.interfaces.IReactorTCP.listenTCP">twisted.internet.interfaces.IReactorTCP.listenTCP</a></code>,
+ and the other <code>IReactor*.listen*</code> APIs for the lower
+ level APIs that endpoints are based on.</p>
+
+ <p>This document will explain each step of the way.</p>
+
+ <h2>Protocols<a name="auto1"/></h2>
+
+ <p>As mentioned above, this, along with auxiliary classes and
+ functions, is where most of the code is. A Twisted protocol
+ handles data in an asynchronous manner: the protocol responds
+ to events as they arrive from the network; the events arrive as
+ calls to methods on the protocol.</p>
+
+ <p>Here is a simple example:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+</pre>
+
+ <p>This is one of the simplest protocols. It simply writes back
+ whatever is written to it, and does not respond to all events. Here is an
+ example of a Protocol responding to another event:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span>(<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;An apple a day keeps the doctor away\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+</pre>
+
+ <p>This protocol responds to the initial connection with a well
+ known quote, and then terminates the connection.</p>
+
+ <p>The connectionMade event is usually where set up of the
+ connection object happens, as well as any initial greetings (as
+ in the QOTD protocol above, which is actually based on RFC
+ 865). The <code>connectionLost</code> event is where tearing down of any
+ connection-specific objects is done. Here is an example:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">factory</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span> = <span class="py-src-variable">factory</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">numProtocols</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">numProtocols</span>+<span class="py-src-number">1</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(
+ <span class="py-src-string">&quot;Welcome! There are currently %d open connections.\n&quot;</span> %
+ (<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">numProtocols</span>,))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">numProtocols</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">numProtocols</span>-<span class="py-src-number">1</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+</pre>
+
+ <p>Here <code>connectionMade</code> and
+ <code>connectionLost</code> cooperate to keep a count of the
+ active protocols in a shared object, the factory. The factory must
+ be passed to <code>Echo.__init__</code> when creating a new
+ instance. The factory is used to share state that exists beyond the
+ lifetime of any given connection. You will see why this object is
+ called a &quot;factory&quot; in the next section.</p>
+
+ <h3>loseConnection() and abortConnection()<a name="auto2"/></h3>
+
+ <p>In the code above, <code>loseConnection</code> is called immediately
+ after writing to the transport. The <code>loseConnection</code> call will
+ close the connection only when all the data has been written by Twisted
+ out to the operating system, so it is safe to use in this case without
+ worrying about transport writes being lost. If
+ a <a href="producers.html" shape="rect">producer</a> is being used with the
+ transport, <code>loseConnection</code> will only close the connection once
+ the producer is unregistered.</p>
+
+ <p>In some cases, waiting until all the data is written out is not what we
+ want. Due to network failures, or bugs or maliciousness in the other side
+ of the connection, data written to the transport may not be deliverable,
+ and so even though <code>loseConnection</code> was called the connection
+ will not be lost. In these cases, <code>abortConnection</code> can be
+ used: it closes the connection immediately, regardless of buffered data
+ that is still unwritten in the transport, or producers that are still
+ registered. Note that <code>abortConnection</code> is only available in
+ Twisted 11.1 and newer.</p>
+
+
+ <h3>Using the Protocol<a name="auto3"/></h3>
+
+ <p>In this section, you will learn how to run a server which uses your
+ <code>Protocol</code>.</p>
+
+ <p>Here is code that will run the QOTD server discussed
+ earlier:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">endpoints</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">TCP4ServerEndpoint</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTDFactory</span>(<span class="py-src-parameter">Factory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">QOTD</span>()
+
+<span class="py-src-comment"># 8007 is the port you want to run under. Choose something &gt;1024</span>
+<span class="py-src-variable">endpoint</span> = <span class="py-src-variable">TCP4ServerEndpoint</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">8007</span>)
+<span class="py-src-variable">endpoint</span>.<span class="py-src-variable">listen</span>(<span class="py-src-variable">QOTDFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+ <p>In this example, I create a protocol <code base="twisted.internet.protocol" class="api">Factory</code>. I want to tell this
+ factory that its job is to build QOTD protocol instances, so I set its
+ <code>buildProtocol</code> method to return instances of the QOTD class. Then, I want to listen
+ on a TCP port, so I make a <code>TCP4ServerEndpoint</code> to identify the
+ port that I want to bind to, and then pass the factory I just created to
+ its <code base="twisted.internet.interfaces.IStreamServerEndpoint" class="api">listen</code>
+ method.</p>
+
+ <p>Because this is a short example, nothing else has yet started up the
+ Twisted reactor. <code>endpoint.listen</code> tells the reactor to handle
+ connections to the endpoint's address using a particular protocol, but the
+ reactor needs to be <em>running</em> in order for it to do anything.
+ <code>reactor.run()</code> starts the reactor and then waits forever for
+ connections to arrive on the port you've specified.</p>
+
+ <p>You can stop the reactor by hitting Control-C in a terminal or calling
+ <code>reactor.stop</code>.</p>
+
+ <p>For more information on different ways you can listen for incoming
+ connections, see <a href="endpoints.html" shape="rect">the documentation for the
+ endpoints API</a>.</p>
+
+ <h3>Helper Protocols<a name="auto4"/></h3>
+
+ <p>Many protocols build upon similar lower-level abstraction.
+ The most popular in internet protocols is being line-based.
+ Lines are usually terminated with a CR-LF combinations.</p>
+
+ <p>However, quite a few protocols are mixed - they have
+ line-based sections and then raw data sections. Examples
+ include HTTP/1.1 and the Freenet protocol.</p>
+
+ <p>For those cases, there is the <code>LineReceiver</code>
+ protocol. This protocol dispatches to two different event
+ handlers - <code>lineReceived</code> and
+ <code>rawDataReceived</code>. By default, only
+ <code>lineReceived</code> will be called, once for each line.
+ However, if <code>setRawMode</code> is called, the protocol
+ will call <code>rawDataReceived</code> until
+ <code>setLineMode</code> is called, which returns it to using
+ <code>lineReceived</code>. It also provides a method,
+ <code>sendLine</code>, that writes data to the transport along
+ with the delimiter the class uses to split lines (by default,
+ <code>\r\n</code>).</p>
+
+ <p>Here is an example for a simple use of the line
+ receiver:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">basic</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LineReceiver</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Answer</span>(<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-variable">answers</span> = {<span class="py-src-string">'How are you?'</span>: <span class="py-src-string">'Fine'</span>, <span class="py-src-variable">None</span> : <span class="py-src-string">&quot;I don't know what you mean&quot;</span>}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">answers</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">answers</span>[<span class="py-src-variable">line</span>])
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">answers</span>[<span class="py-src-variable">None</span>])
+</pre>
+
+ <p>Note that the delimiter is not part of the line.</p>
+
+ <p>Several other, less popular, helpers exist, such as a
+ netstring based protocol and a prefixed-message-length
+ protocol.</p>
+
+ <h3>State Machines<a name="auto5"/></h3>
+
+ <p>Many Twisted protocol handlers need to write a state machine
+ to record the state they are at. Here are some pieces of advice
+ which help to write state machines:</p>
+
+ <ul>
+ <li>Don't write big state machines. Prefer to write a state
+ machine which deals with one level of abstraction at a
+ time.</li>
+
+ <li>Don't mix application-specific code with Protocol
+ handling code. When the protocol handler has to make an
+ application-specific call, keep it as a method call.</li>
+ </ul>
+
+ <h2>Factories<a name="auto6"/></h2>
+
+ <h3>Simpler Protocol Creation<a name="auto7"/></h3>
+
+ <p>For a factory which simply instantiates instances of a
+ specific protocol class, there is a simpler way to implement the factory.
+ The default implementation of the <code>buildProtocol</code> method calls
+ the <code>protocol</code> attribute of the factory to create
+ a <code>Protocol</code> instance, and then sets an attribute on it
+ called <code>factory</code> which points to the factory
+ itself. This lets every <code>Protocol</code> access, and possibly
+ modify, the persistent configuration. Here is an example that uses these
+ features instead of overriding <code>buildProtocol</code>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">endpoints</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">TCP4ServerEndpoint</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span>(<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-comment"># self.factory was set by the factory's default buildProtocol:</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">quote</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTDFactory</span>(<span class="py-src-parameter">Factory</span>):
+
+ <span class="py-src-comment"># This will be used by the default buildProtocol to create new protocols:</span>
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">QOTD</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">quote</span>=<span class="py-src-parameter">None</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">quote</span> = <span class="py-src-variable">quote</span> <span class="py-src-keyword">or</span> <span class="py-src-string">'An apple a day keeps the doctor away'</span>
+
+<span class="py-src-variable">endpoint</span> = <span class="py-src-variable">TCP4ServerEndpoint</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">8007</span>)
+<span class="py-src-variable">endpoint</span>.<span class="py-src-variable">listen</span>(<span class="py-src-variable">QOTDFactory</span>(<span class="py-src-string">&quot;configurable quote&quot;</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h3>Factory Startup and Shutdown<a name="auto8"/></h3>
+
+ <p>A Factory has two methods to perform application-specific
+ building up and tearing down (since a Factory is frequently
+ persisted, it is often not appropriate to do them in <code>__init__</code>
+ or <code>__del__</code>, and would frequently be too early or too late).</p>
+
+ <p>Here is an example of a factory which allows its Protocols
+ to write to a special log-file:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">basic</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LineReceiver</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LoggingProtocol</span>(<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">fp</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">line</span>+<span class="py-src-string">'\n'</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LogfileFactory</span>(<span class="py-src-parameter">Factory</span>):
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">LoggingProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">fileName</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">file</span> = <span class="py-src-variable">fileName</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">fp</span> = <span class="py-src-variable">open</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">file</span>, <span class="py-src-string">'a'</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">fp</span>.<span class="py-src-variable">close</span>()
+</pre>
+
+ <h2>Putting it All Together<a name="auto9"/></h2>
+
+ <p>As a final example, here's a simple chat server that allows
+ users to choose a username and then communicate with other
+ users. It demonstrates the use of shared state in the factory, a
+ state machine for each individual protocol, and communication
+ between different protocols.</p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">basic</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LineReceiver</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Chat</span>(<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">users</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">state</span> = <span class="py-src-string">&quot;GETNAME&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-string">&quot;What's your name?&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>):
+ <span class="py-src-keyword">del</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>]
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">state</span> == <span class="py-src-string">&quot;GETNAME&quot;</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">handle_GETNAME</span>(<span class="py-src-variable">line</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">handle_CHAT</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">handle_GETNAME</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-string">&quot;Name taken, please choose another.&quot;</span>)
+ <span class="py-src-keyword">return</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-string">&quot;Welcome, %s!&quot;</span> % (<span class="py-src-variable">name</span>,))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">name</span>] = <span class="py-src-variable">self</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">state</span> = <span class="py-src-string">&quot;CHAT&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">handle_CHAT</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">message</span> = <span class="py-src-string">&quot;&lt;%s&gt; %s&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">message</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">name</span>, <span class="py-src-variable">protocol</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">iteritems</span>():
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">protocol</span> != <span class="py-src-variable">self</span>:
+ <span class="py-src-variable">protocol</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">message</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatFactory</span>(<span class="py-src-parameter">Factory</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {} <span class="py-src-comment"># maps user names to Chat instances</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">Chat</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>)
+
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8123</span>, <span class="py-src-variable">ChatFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/servers/chat.py"><span class="filename">listings/servers/chat.py</span></a></div></div>
+
+ <p>The only API you might not be familiar with
+ is <code>listenTCP</code>. <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTCP.listenTCP.html" title="twisted.internet.interfaces.IReactorTCP.listenTCP">listenTCP</a></code> is
+ the method which connects a <code>Factory</code> to the network.
+ This is the lower-level API
+ that <a href="endpoints.html" shape="rect">endpoints</a> wraps for you.</p>
+
+ <p>Here's a sample transcript of a chat session (<em><strong>this</strong></em> is text entered by the user):</p>
+
+<pre class="shell" xml:space="preserve">
+$ <em><strong>telnet 127.0.0.1 8123</strong></em>
+Trying 127.0.0.1...
+Connected to 127.0.0.1.
+Escape character is '^]'.
+What's your name?
+<em><strong>test</strong></em>
+Name taken, please choose another.
+<em><strong>bob</strong></em>
+Welcome, bob!
+<em><strong>hello</strong></em>
+&lt;alice&gt; hi bob
+<em><strong>twisted makes writing servers so easy!</strong></em>
+&lt;alice&gt; I couldn't agree more
+&lt;carrol&gt; yeah, it's great
+</pre>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/ssl.html b/doc/core/howto/ssl.html
new file mode 100644
index 0000000..6744ad3
--- /dev/null
+++ b/doc/core/howto/ssl.html
@@ -0,0 +1,550 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using SSL in Twisted</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using SSL in Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">SSL echo server and client without client authentication</a></li><ul><li><a href="#auto2">SSL echo server</a></li><li><a href="#auto3">SSL echo client</a></li></ul><li><a href="#auto4">Using startTLS</a></li><ul><li><a href="#auto5">startTLS server</a></li><li><a href="#auto6">startTLS client</a></li></ul><li><a href="#auto7">Client authentication</a></li><ul><li><a href="#auto8">Client-authenticating server</a></li><li><a href="#auto9">Client with certificates</a></li></ul><li><a href="#auto10">Other facilities</a></li><li><a href="#auto11">Conclusion</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>This document describes how to use SSL in Twisted servers and clients. It
+ assumes that you know what SSL is, what some of the major reasons to use it
+ are, and how to generate your own SSL certificates, in particular self-signed
+ certificates. It also assumes that you are comfortable with creating TCP
+ servers and clients as described in the <a href="servers.html" shape="rect">server howto
+ </a> and <a href="clients.html" shape="rect">client howto</a>. After reading this
+ document you should be able to create servers and clients that can use SSL to
+ encrypt their connections, switch from using an unencrypted channel to an
+ encrypted one mid-connection, and require client authentication.</p>
+
+ <p>Using SSL in Twisted requires that you have
+ <a href="http://pyopenssl.sf.net" shape="rect">pyOpenSSL</a> installed. A quick test to
+ verify that you do is to run <code>from OpenSSL import SSL</code> at a
+ python prompt and not get an error.</p>
+
+ <p>SSL connections require SSL contexts. These contexts are generated by a
+ <code>ContextFactory</code> that maintains state like the SSL method, private
+ key file name, and certificate file name.</p>
+
+ <p>Instead of using listenTCP and connectTCP to create a connection, use
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorSSL.listenSSL.html" title="twisted.internet.interfaces.IReactorSSL.listenSSL">listenSSL</a></code> and
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorSSL.connectSSL.html" title="twisted.internet.interfaces.IReactorSSL.connectSSL">connectSSL</a></code> for a
+ server and client respectively. These methods take a contextFactory as an
+ additional argument.</p>
+
+ <p>The basic server context factory is
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.ssl.ContextFactory.html" title="twisted.internet.ssl.ContextFactory">twisted.internet.ssl.ContextFactory</a></code>, and the basic
+ client context factory is
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.ssl.ClientContextFactory.html" title="twisted.internet.ssl.ClientContextFactory">twisted.internet.ssl.ClientContextFactory</a></code>. They can
+ be used as-is or subclassed.
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.ssl.DefaultOpenSSLContextFactory.html" title="twisted.internet.ssl.DefaultOpenSSLContextFactory">twisted.internet.ssl.DefaultOpenSSLContextFactory</a></code>
+ is a convenience server class that subclasses <code>ContextFactory</code>
+ and adds default parameters to the SSL handshake and connection. Another
+ useful class is
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.ssl.CertificateOptions.html" title="twisted.internet.ssl.CertificateOptions">twisted.internet.ssl.CertificateOptions</a></code>; it is a
+ factory for SSL context objects that lets you specify many of the common
+ verification and session options so it can do the proper pyOpenSSL
+ initialization for you.</p>
+
+ <p>Those are the big immediate differences between TCP and SSL connections,
+ so let's look at an example. In it and all subsequent examples it is assumed
+ that keys and certificates for the server, certificate authority, and client
+ should they exist live in a <i>keys/</i> subdirectory of the directory
+ containing the example code, and that the certificates are self-signed.</p>
+
+ <h2>SSL echo server and client without client authentication<a name="auto1"/></h2>
+
+ <p>Authentication and encryption are two separate parts of the SSL protocol.
+ The server almost always needs a key and certificate to authenticate itself
+ to the client but is usually configured to allow encrypted connections with
+ unauthenticated clients who don't have certificates. This common case is
+ demonstrated first by adding SSL support to the echo client and server in
+ the <a href="../examples/index.html" shape="rect">core examples</a>.</p>
+
+ <h3>SSL echo server<a name="auto2"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ssl</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-string">&quot;&quot;&quot;As soon as any data is received, write it back.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">Factory</span>()
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">Echo</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenSSL</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>,
+ <span class="py-src-variable">ssl</span>.<span class="py-src-variable">DefaultOpenSSLContextFactory</span>(
+ <span class="py-src-string">'keys/server.key'</span>, <span class="py-src-string">'keys/server.crt'</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h3>SSL echo client<a name="auto3"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ssl</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ClientFactory</span>, <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClient</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;hello, world&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;hello, world!&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Server said:&quot;</span>, <span class="py-src-variable">data</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClientFactory</span>(<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">EchoClient</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Connection failed - goodbye!&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Connection lost - goodbye!&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">EchoClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectSSL</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>, <span class="py-src-variable">ssl</span>.<span class="py-src-variable">ClientContextFactory</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Contexts are created according to a specified method.
+ <code>SSLv3_METHOD</code>, <code>SSLv23_METHOD</code>, and
+ <code>TLSv1_METHOD</code> are the valid constants that represent SSL methods
+ to use when creating a context object. <code>DefaultOpenSSLContextFactory</code> and
+ <code>ClientContextFactory</code> default to using <code>SSL.SSLv23_METHOD</code> as their
+ method, and it is compatible for communication with all the other methods
+ listed above. An older method constant, <code>SSLv2_METHOD</code>, exists but
+ is explicitly disallowed in both <code>DefaultOpenSSLContextFactory</code> and
+ <code>ClientContextFactory</code> for being insecure by calling
+ <code>set_options(SSL.OP_NO_SSLv2)</code> on their contexts. See
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.ssl.html" title="twisted.internet.ssl">twisted.internet.ssl</a></code> for additional comments.</p>
+
+ <h2>Using startTLS<a name="auto4"/></h2>
+
+ <p>If you want to switch from unencrypted to encrypted traffic
+ mid-connection, you'll need to turn on SSL with <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.ITLSTransport.startTLS.html" title="twisted.internet.interfaces.ITLSTransport.startTLS">startTLS</a></code> on both
+ ends of the connection at the same time via some agreed-upon signal like the
+ reception of a particular message. You can readily verify the switch to an
+ encrypted channel by examining the packet payloads with a tool like
+ <a href="http://www.wireshark.org/" shape="rect">Wireshark</a>.</p>
+
+ <h3>startTLS server<a name="auto5"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">ssl</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ServerFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">basic</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LineReceiver</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">TLSServer</span>(<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;received: &quot;</span> + <span class="py-src-variable">line</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">line</span> == <span class="py-src-string">&quot;STARTTLS&quot;</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;-- Switching to TLS&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-string">'READY'</span>)
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">ServerTLSContext</span>(
+ <span class="py-src-variable">privateKeyFileName</span>=<span class="py-src-string">'keys/server.key'</span>,
+ <span class="py-src-variable">certificateFileName</span>=<span class="py-src-string">'keys/server.crt'</span>,
+ )
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">startTLS</span>(<span class="py-src-variable">ctx</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ServerTLSContext</span>(<span class="py-src-parameter">ssl</span>.<span class="py-src-parameter">DefaultOpenSSLContextFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, *<span class="py-src-parameter">args</span>, **<span class="py-src-parameter">kw</span>):
+ <span class="py-src-variable">kw</span>[<span class="py-src-string">'sslmethod'</span>] = <span class="py-src-variable">SSL</span>.<span class="py-src-variable">TLSv1_METHOD</span>
+ <span class="py-src-variable">ssl</span>.<span class="py-src-variable">DefaultOpenSSLContextFactory</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>, *<span class="py-src-variable">args</span>, **<span class="py-src-variable">kw</span>)
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">TLSServer</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h3>startTLS client<a name="auto6"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">ssl</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ClientFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">basic</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LineReceiver</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientTLSContext</span>(<span class="py-src-parameter">ssl</span>.<span class="py-src-parameter">ClientContextFactory</span>):
+ <span class="py-src-variable">isClient</span> = <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getContext</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">SSL</span>.<span class="py-src-variable">Context</span>(<span class="py-src-variable">SSL</span>.<span class="py-src-variable">TLSv1_METHOD</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">TLSClient</span>(<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-variable">pretext</span> = [
+ <span class="py-src-string">&quot;first line&quot;</span>,
+ <span class="py-src-string">&quot;last thing before TLS starts&quot;</span>,
+ <span class="py-src-string">&quot;STARTTLS&quot;</span>]
+
+ <span class="py-src-variable">posttext</span> = [
+ <span class="py-src-string">&quot;first thing after TLS started&quot;</span>,
+ <span class="py-src-string">&quot;last thing ever&quot;</span>]
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">l</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">pretext</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">l</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;received: &quot;</span> + <span class="py-src-variable">line</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">line</span> == <span class="py-src-string">&quot;READY&quot;</span>:
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">ClientTLSContext</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">startTLS</span>(<span class="py-src-variable">ctx</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">l</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">posttext</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">l</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">TLSClientFactory</span>(<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">TLSClient</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;connection failed: &quot;</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">getErrorMessage</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;connection lost: &quot;</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">getErrorMessage</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">TLSClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p><code>startTLS</code> is a transport method that gets passed a context.
+ It is invoked at an agreed-upon time in the data reception method of the
+ client and server protocols. The <code>ServerTLSContext</code> and
+ <code>ClientTLSContext</code> classes used above inherit from the basic
+ server and client context factories used in the earlier echo examples and
+ illustrate two more ways of setting an SSL method.</p>
+
+ <h2>Client authentication<a name="auto7"/></h2>
+
+ <p>Server and client-side changes to require client authentication fall
+ largely under the dominion of pyOpenSSL, but few examples seem to exist on
+ the web so for completeness a sample server and client are provided here.</p>
+
+ <h3>Client-authenticating server<a name="auto8"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ssl</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">verifyCallback</span>(<span class="py-src-parameter">connection</span>, <span class="py-src-parameter">x509</span>, <span class="py-src-parameter">errnum</span>, <span class="py-src-parameter">errdepth</span>, <span class="py-src-parameter">ok</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">ok</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'invalid cert from subject:'</span>, <span class="py-src-variable">x509</span>.<span class="py-src-variable">get_subject</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">False</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Certs are fine&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">True</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">Factory</span>()
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">Echo</span>
+
+ <span class="py-src-variable">myContextFactory</span> = <span class="py-src-variable">ssl</span>.<span class="py-src-variable">DefaultOpenSSLContextFactory</span>(
+ <span class="py-src-string">'keys/server.key'</span>, <span class="py-src-string">'keys/server.crt'</span>
+ )
+
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">myContextFactory</span>.<span class="py-src-variable">getContext</span>()
+
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">set_verify</span>(
+ <span class="py-src-variable">SSL</span>.<span class="py-src-variable">VERIFY_PEER</span> | <span class="py-src-variable">SSL</span>.<span class="py-src-variable">VERIFY_FAIL_IF_NO_PEER_CERT</span>,
+ <span class="py-src-variable">verifyCallback</span>
+ )
+
+ <span class="py-src-comment"># Since we have self-signed certs we have to explicitly</span>
+ <span class="py-src-comment"># tell the server to trust them.</span>
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">load_verify_locations</span>(<span class="py-src-string">&quot;keys/ca.pem&quot;</span>)
+
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenSSL</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>, <span class="py-src-variable">myContextFactory</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Use the <code>set_verify</code> method to set the verification mode for a
+ context object and the verification callback. The mode is either
+ <code>VERIFY_NONE</code> or <code>VERIFY_PEER</code>. If
+ <code>VERIFY_PEER</code> is set, the mode can be augmented by
+ <code>VERIFY_FAIL_IF_NO_PEER_CERT</code> and/or
+ <code>VERIFY_CLIENT_ONCE</code>.</p>
+
+ <p>The callback takes as its arguments a connection object, X509 object,
+ error number, error depth, and return code. The purpose of the callback is
+ to allow you to enforce additional restrictions on the verification. Thus,
+ if the return code is False, you should return False; if the return code is
+ True <i>and</i> further verification passes, return True.</p>
+
+
+ <h3>Client with certificates<a name="auto9"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ssl</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ClientFactory</span>, <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClient</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;hello, world&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;hello, world!&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Server said:&quot;</span>, <span class="py-src-variable">data</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClientFactory</span>(<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">EchoClient</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Connection failed - goodbye!&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Connection lost - goodbye!&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CtxFactory</span>(<span class="py-src-parameter">ssl</span>.<span class="py-src-parameter">ClientContextFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getContext</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">method</span> = <span class="py-src-variable">SSL</span>.<span class="py-src-variable">SSLv23_METHOD</span>
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">ssl</span>.<span class="py-src-variable">ClientContextFactory</span>.<span class="py-src-variable">getContext</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_certificate_file</span>(<span class="py-src-string">'keys/client.crt'</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_privatekey_file</span>(<span class="py-src-string">'keys/client.key'</span>)
+
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">ctx</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">EchoClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectSSL</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>, <span class="py-src-variable">CtxFactory</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h2>Other facilities<a name="auto10"/></h2>
+
+ <p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.protocols.amp.html" title="twisted.protocols.amp">twisted.protocols.amp</a></code> supports encrypted
+ connections and exposes a <code>startTLS</code> method one can use or
+ subclass. <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.html" title="twisted.web">twisted.web</a></code> has built-in SSL support in
+ its <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.html" title="twisted.web.client">client</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.http.html" title="twisted.web.http">http</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.xmlrpc.html" title="twisted.web.xmlrpc">xmlrpc</a></code> modules.</p>
+
+ <h2>Conclusion<a name="auto11"/></h2>
+
+ <p>After reading through this tutorial, you should be able to: </p>
+ <ul>
+ <li>Use <code>listenSSL</code> and <code>connectSSL</code> to create servers and clients that use
+ SSL</li>
+ <li>Use <code>startTLS</code> to switch a channel from being unencrypted to using SSL
+ mid-connection</li>
+ <li>Add server and client support for client authentication</li>
+ </ul>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/stylesheet-unprocessed.css b/doc/core/howto/stylesheet-unprocessed.css
new file mode 100644
index 0000000..e4a62cc
--- /dev/null
+++ b/doc/core/howto/stylesheet-unprocessed.css
@@ -0,0 +1,20 @@
+
+span.footnote {
+ vertical-align: super;
+ font-size: small;
+}
+
+span.footnote:before
+{
+ content: "[Footnote: ";
+}
+
+span.footnote:after
+{
+ content: "]";
+}
+
+div.note:before
+{
+ content: "Note: ";
+}
diff --git a/doc/core/howto/stylesheet.css b/doc/core/howto/stylesheet.css
new file mode 100644
index 0000000..3c5961e
--- /dev/null
+++ b/doc/core/howto/stylesheet.css
@@ -0,0 +1,189 @@
+
+body
+{
+ margin-left: 2em;
+ margin-right: 2em;
+ border: 0px;
+ padding: 0px;
+ font-family: sans-serif;
+ }
+
+.done { color: #005500; background-color: #99ff99 }
+.notdone { color: #550000; background-color: #ff9999;}
+
+pre
+{
+ padding: 1em;
+ border: thin black solid;
+ line-height: 1.2em;
+}
+
+.boxed
+{
+ padding: 1em;
+ border: thin black solid;
+}
+
+.shell
+{
+ background-color: #ffffdd;
+}
+
+.python
+{
+ background-color: #dddddd;
+}
+
+.htmlsource
+{
+ background-color: #dddddd;
+}
+
+.py-prototype
+{
+ background-color: #ddddff;
+}
+
+
+.python-interpreter
+{
+ background-color: #ddddff;
+}
+
+.doit
+{
+ border: thin blue dashed ;
+ background-color: #0ef
+}
+
+.py-src-comment
+{
+ color: #1111CC
+}
+
+.py-src-keyword
+{
+ color: #3333CC;
+ font-weight: bold;
+ line-height: 1.0em
+}
+
+.py-src-parameter
+{
+ color: #000066;
+ font-weight: bold;
+ line-height: 1.0em
+}
+
+.py-src-identifier
+{
+ color: #CC0000
+}
+
+.py-src-string
+{
+
+ color: #115511
+}
+
+.py-src-endmarker
+{
+ display: block; /* IE hack; prevents following line from being sucked into the py-listing box. */
+}
+
+.py-linenumber
+{
+ background-color: #cdcdcd;
+ float: left;
+ margin-top: 0px;
+ width: 4.0em
+}
+
+.py-listing, .html-listing, .listing
+{
+ margin: 1ex;
+ border: thin solid black;
+ background-color: #eee;
+}
+
+.py-listing pre, .html-listing pre, .listing pre
+{
+ margin: 0px;
+ border: none;
+ border-bottom: thin solid black;
+}
+
+.py-listing .python
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.html-listing .htmlsource
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.caption
+{
+ text-align: center;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+}
+
+.filename
+{
+ font-style: italic;
+ }
+
+.manhole-output
+{
+ color: blue;
+}
+
+hr
+{
+ display: inline;
+ }
+
+ul
+{
+ padding: 0px;
+ margin: 0px;
+ margin-left: 1em;
+ padding-left: 1em;
+ border-left: 1em;
+ }
+
+li
+{
+ padding: 2px;
+ }
+
+dt
+{
+ font-weight: bold;
+ margin-left: 1ex;
+ }
+
+dd
+{
+ margin-bottom: 1em;
+ }
+
+div.note
+{
+ background-color: #FFFFCC;
+ margin-top: 1ex;
+ margin-left: 5%;
+ margin-right: 5%;
+ padding-top: 1ex;
+ padding-left: 5%;
+ padding-right: 5%;
+ border: thin black solid;
+}
diff --git a/doc/core/howto/tap.html b/doc/core/howto/tap.html
new file mode 100644
index 0000000..1441cb5
--- /dev/null
+++ b/doc/core/howto/tap.html
@@ -0,0 +1,323 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Writing a twistd Plugin</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Writing a twistd Plugin</h1>
+ <div class="toc"><ol><li><a href="#auto0">Goals</a></li><li><a href="#auto1">Alternatives to twistd plugins</a></li><li><a href="#auto2">Creating the plugin</a></li><li><a href="#auto3">Using cred with your TAP</a></li><li><a href="#auto4">Conclusion</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p>This document describes adding subcommands to
+the <code>twistd</code> command, as a way to facilitate the deployment
+of your applications. <em>(This feature was added in Twisted 2.5)</em></p>
+
+<p>The target audience of this document are those that have developed
+a Twisted application which needs a command line-based deployment
+mechanism.</p>
+
+<p>There are a few prerequisites to understanding this document:</p>
+<ul>
+ <li>A basic understanding of the Twisted Plugin System (i.e.,
+ the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.plugin.html" title="twisted.plugin">twisted.plugin</a></code> module) is
+ necessary, however, step-by-step instructions will be
+ given. Reading <a href="plugin.html" shape="rect">The Twisted Plugin
+ System</a> is recommended, in particular the <q>Extending an
+ Existing Program</q> section.</li>
+ <li>The <a href="application.html" shape="rect">Application</a> infrastructure
+ is used in <code>twistd</code> plugins; in particular, you should
+ know how to expose your program's functionality as a Service.</li>
+ <li>In order to parse command line arguments, the <code>twistd</code> plugin
+ mechanism relies
+ on <code>twisted.python.usage</code>, which is documented
+ in <a href="options.html" shape="rect">Using usage.Options</a>.</li>
+</ul>
+
+<h2>Goals<a name="auto0"/></h2>
+
+<p>After reading this document, the reader should be able to expose
+their Service-using application as a subcommand
+of <code>twistd</code>, taking into consideration whatever was passed
+on the command line.</p>
+
+<h2>Alternatives to twistd plugins<a name="auto1"/></h2>
+<p>The major alternative to the twistd plugin mechanism is the <code>.tac</code>
+file, which is a simple script to be used with the
+twistd <code>-y/--python</code> parameter. The twistd plugin mechanism
+exists to offer a more extensible command-line-driven interface to
+your application. For more information on <code>.tac</code> files, see
+the document <a href="application.html" shape="rect">Using the Twisted Application
+Framework</a>.</p>
+
+
+<h2>Creating the plugin<a name="auto2"/></h2>
+
+<p>The following directory structure is assumed of your project:</p>
+
+<ul>
+ <li><strong>MyProject</strong> - Top level directory
+ <ul>
+ <li><strong>myproject</strong> - Python package
+ <ul><li><strong>__init__.py</strong></li></ul>
+ </li>
+ </ul>
+ </li>
+</ul>
+
+<p>
+ During development of your project, Twisted plugins can be loaded
+ from a special directory in your project, assuming your top level
+ directory ends up in sys.path. Create a directory
+ named <code>twisted</code> containing a directory
+ named <code>plugins</code>, and add a file
+ named <code>myproject_plugin.py</code> to it. This file will contain your
+ plugin. Note that you should <em>not</em> add any __init__.py files
+ to this directory structure, and the plugin file should <em>not</em>
+ be named <code>myproject.py</code> (because that would conflict with
+ your project's module name).
+</p>
+
+<p>
+ In this file, define an object which <em>provides</em> the interfaces
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.plugin.IPlugin.html" title="twisted.plugin.IPlugin">twisted.plugin.IPlugin</a></code>
+ and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IServiceMaker.html" title="twisted.application.service.IServiceMaker">twisted.application.service.IServiceMaker</a></code>.
+</p>
+
+<p>The <code>tapname</code> attribute of your IServiceMaker provider
+will be used as the subcommand name in a command
+like <code class="shell">twistd [subcommand] [args...]</code>, and
+the <code>options</code> attribute (which should be
+a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.usage.Options.html" title="twisted.python.usage.Options">usage.Options</a></code>
+subclass) will be used to parse the given args.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">service</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IServiceMaker</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myproject</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">MyFactory</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">&quot;port&quot;</span>, <span class="py-src-string">&quot;p&quot;</span>, <span class="py-src-number">1235</span>, <span class="py-src-string">&quot;The port number to listen on.&quot;</span>]]
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyServiceMaker</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IServiceMaker</span>, <span class="py-src-variable">IPlugin</span>)
+ <span class="py-src-variable">tapname</span> = <span class="py-src-string">&quot;myproject&quot;</span>
+ <span class="py-src-variable">description</span> = <span class="py-src-string">&quot;Run this! It'll make your dog happy.&quot;</span>
+ <span class="py-src-variable">options</span> = <span class="py-src-variable">Options</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">options</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Construct a TCPServer from a factory defined in myproject.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">options</span>[<span class="py-src-string">&quot;port&quot;</span>]), <span class="py-src-variable">MyFactory</span>())
+
+
+<span class="py-src-comment"># Now construct an object which *provides* the relevant interfaces</span>
+<span class="py-src-comment"># The name of this variable is irrelevant, as long as there is *some*</span>
+<span class="py-src-comment"># name bound to a provider of IPlugin and IServiceMaker.</span>
+
+<span class="py-src-variable">serviceMaker</span> = <span class="py-src-variable">MyServiceMaker</span>()
+</pre>
+
+<p>
+ Now running <code class="shell">twistd --help</code> should
+ print <code>myproject</code> in the list of available subcommands,
+ followed by the description that we specified in the
+ plugin. <code class="shell">twistd -n myproject</code> would,
+ assuming we defined a <code>MyFactory</code> factory
+ inside <code>myproject</code>, start a listening server on port 1235
+ with that factory.
+</p>
+
+<h2>Using cred with your TAP<a name="auto3"/></h2>
+
+<p>
+ Twisted ships with a robust authentication framework to use with
+ your application. If your server needs authentication functionality,
+ and you haven't read about <a href="cred.html" shape="rect">twisted.cred</a>
+ yet, read up on it first.
+</p>
+
+<p>
+ If you are building a twistd plugin and you want to support a wide
+ variety of authentication patterns, Twisted provides an easy-to-use
+ mixin for your Options subclass:
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.strcred.AuthOptionMixin.html" title="twisted.cred.strcred.AuthOptionMixin">strcred.AuthOptionMixin</a></code>.
+ The following code is an example of using this mixin:
+</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>, <span class="py-src-variable">portal</span>, <span class="py-src-variable">strcred</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">service</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IServiceMaker</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myserver</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">myservice</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ServerOptions</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>, <span class="py-src-parameter">strcred</span>.<span class="py-src-parameter">AuthOptionMixin</span>):
+ <span class="py-src-comment"># This part is optional; it tells AuthOptionMixin what</span>
+ <span class="py-src-comment"># kinds of credential interfaces the user can give us.</span>
+ <span class="py-src-variable">supportedInterfaces</span> = (<span class="py-src-variable">credentials</span>.<span class="py-src-variable">IUsernamePassword</span>,)
+
+ <span class="py-src-variable">optParameters</span> = [
+ [<span class="py-src-string">&quot;port&quot;</span>, <span class="py-src-string">&quot;p&quot;</span>, <span class="py-src-number">1234</span>, <span class="py-src-string">&quot;Server port number&quot;</span>],
+ [<span class="py-src-string">&quot;host&quot;</span>, <span class="py-src-string">&quot;h&quot;</span>, <span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-string">&quot;Server hostname&quot;</span>]]
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyServerServiceMaker</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IServiceMaker</span>, <span class="py-src-variable">IPlugin</span>)
+ <span class="py-src-variable">tapname</span> = <span class="py-src-string">&quot;myserver&quot;</span>
+ <span class="py-src-variable">description</span> = <span class="py-src-string">&quot;This server does nothing productive.&quot;</span>
+ <span class="py-src-variable">options</span> = <span class="py-src-variable">ServerOptions</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">options</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Construct a service object.&quot;&quot;&quot;</span>
+ <span class="py-src-comment"># The realm is a custom object that your server defines.</span>
+ <span class="py-src-variable">realm</span> = <span class="py-src-variable">myservice</span>.<span class="py-src-variable">MyServerRealm</span>(<span class="py-src-variable">options</span>[<span class="py-src-string">&quot;host&quot;</span>])
+
+ <span class="py-src-comment"># The portal is something Cred can provide, as long as</span>
+ <span class="py-src-comment"># you have a list of checkers that you'll support. This</span>
+ <span class="py-src-comment"># list is provided my AuthOptionMixin.</span>
+ <span class="py-src-variable">portal</span> = <span class="py-src-variable">portal</span>.<span class="py-src-variable">Portal</span>(<span class="py-src-variable">realm</span>, <span class="py-src-variable">options</span>[<span class="py-src-string">&quot;credCheckers&quot;</span>])
+
+ <span class="py-src-comment"># OR, if you know you might get multiple interfaces, and</span>
+ <span class="py-src-comment"># only want to give your application one of them, you</span>
+ <span class="py-src-comment"># also have that option with AuthOptionMixin:</span>
+ <span class="py-src-variable">interface</span> = <span class="py-src-variable">credentials</span>.<span class="py-src-variable">IUsernamePassword</span>
+ <span class="py-src-variable">portal</span> = <span class="py-src-variable">portal</span>.<span class="py-src-variable">Portal</span>(<span class="py-src-variable">realm</span>, <span class="py-src-variable">options</span>[<span class="py-src-string">&quot;credInterfaces&quot;</span>][<span class="py-src-variable">interface</span>])
+
+ <span class="py-src-comment"># The protocol factory is, like the realm, something you implement.</span>
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">myservice</span>.<span class="py-src-variable">ServerFactory</span>(<span class="py-src-variable">realm</span>, <span class="py-src-variable">portal</span>)
+
+ <span class="py-src-comment"># Finally, return a service that will listen for connections.</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">options</span>[<span class="py-src-string">&quot;port&quot;</span>]), <span class="py-src-variable">factory</span>)
+
+
+<span class="py-src-comment"># As in our example above, we have to construct an object that</span>
+<span class="py-src-comment"># provides the IPlugin and IServiceMaker interfaces.</span>
+
+<span class="py-src-variable">serviceMaker</span> = <span class="py-src-variable">MyServerServiceMaker</span>()
+</pre>
+
+<p>
+ Now that you have your TAP configured to support any authentication
+ we can throw at it, you're ready to use it. Here is an example of
+ starting your server using the /etc/passwd file for
+ authentication. (Clearly, this won't work on servers with shadow
+ passwords.)
+</p>
+
+<pre class="shell" xml:space="preserve">
+$ twistd myserver --auth passwd:/etc/passwd
+</pre>
+
+<p>
+ For a full list of cred plugins supported, see <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.plugins.html" title="twisted.plugins">twisted.plugins</a></code>, or use the command-line help:
+</p>
+
+<pre class="shell" xml:space="preserve">
+$ twistd myserver --help-auth
+$ twistd myserver --help-auth-type passwd
+</pre>
+
+<h2>Conclusion<a name="auto4"/></h2>
+
+<p>You should now be able to</p>
+<ul>
+ <li>Create a twistd plugin</li>
+ <li>Incorporate authentication into your plugin</li>
+ <li>Use it from your development environment</li>
+ <li>Install it correctly and use it in deployment</li>
+</ul>
+
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/template.tpl b/doc/core/howto/template.tpl
new file mode 100644
index 0000000..1fbb517
--- /dev/null
+++ b/doc/core/howto/template.tpl
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+<title>Twisted Documentation: </title>
+<link type="text/css" rel="stylesheet"
+href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title"></h1>
+ <div class="toc"></div>
+ <div class="body">
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: </span>
+ </body>
+</html>
+
diff --git a/doc/core/howto/testing.html b/doc/core/howto/testing.html
new file mode 100644
index 0000000..01662b1
--- /dev/null
+++ b/doc/core/howto/testing.html
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Writing tests for Twisted code using Trial</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Writing tests for Twisted code using Trial</h1>
+ <div class="toc"><ol><li><a href="#auto0">Trial basics</a></li><li><a href="#auto1">Trial directories</a></li><li><a href="#auto2">Twisted-specific quirks: reactor, Deferreds, callLater</a></li><ul><li><a href="#auto3">Leave the Reactor as you found it</a></li><li><a href="#auto4">Using Timers to Detect Failing Tests</a></li><li><a href="#auto5">Interacting with warnings in tests</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Trial basics<a name="auto0"/></h2>
+
+<p><strong>Trial</strong> is Twisted's testing framework. It provides a
+library for writing test cases and utility functions for working with the
+Twisted environment in your tests, and a command-line utility for running your
+tests. Trial is built on the Python standard library's <code>unittest</code>
+module.</p>
+
+<p>To run all the Twisted tests, do:</p>
+
+<pre class="shell" xml:space="preserve">
+$ trial twisted
+</pre>
+
+<p>Refer to the Trial man page for other command-line options.</p>
+
+<h2>Trial directories<a name="auto1"/></h2>
+
+<p>You might notice a new <code class="shell">_trial_temp</code> folder in the
+current working directory after Trial completes the tests. This folder is the
+working directory for the Trial process. It can be used by unit tests and
+allows them to write whatever data they like to disk, and not worry
+about polluting the current working directory.</p>
+
+<p>Folders named <code class="shell">_trial_temp-&lt;counter&gt;</code> are
+created if two instances of Trial are run in parallel from the same directory,
+so as to avoid giving two different test-runs the same temporary directory.</p>
+
+<p>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.lockfile.html" title="twisted.python.lockfile">twisted.python.lockfile</a></code> utility is used to lock
+the <code class="shell">_trial_temp</code> directories. On Linux, this results
+in symlinks to pids. On Windows, directories are created with a single file with
+a pid as the contents. These lock files will be cleaned up if Trial exits normally
+and otherwise they will be left behind. They should be cleaned up the next time
+Trial tries to use the directory they lock, but it's also safe to delete them
+manually if desired.</p>
+
+<h2>Twisted-specific quirks: reactor, Deferreds, callLater<a name="auto2"/></h2>
+
+<p>The standard Python <code>unittest</code> framework, from which Trial is
+derived, is ideal for testing code with a fairly linear flow of control.
+Twisted is an asynchronous networking framework which provides a clean,
+sensible way to establish functions that are run in response to events (like
+timers and incoming data), which creates a highly non-linear flow of control.
+Trial has a few extensions which help to test this kind of code. This section
+provides some hints on how to use these extensions and how to best structure
+your tests.</p>
+
+<h3>Leave the Reactor as you found it<a name="auto3"/></h3>
+
+<p>Trial runs the entire test suite (over four thousand tests) in a single
+process, with a single reactor. Therefore it is important that your test
+leave the reactor in the same state as it found it. Leftover timers may
+expire during somebody else's unsuspecting test. Leftover connection attempts
+may complete (and fail) during a later test. These lead to intermittent
+failures that wander from test to test and are very time-consuming to track
+down.</p>
+
+<p>If your test leaves event sources in the reactor, Trial will fail the test.
+The <code>tearDown</code> method is a good place to put cleanup code: it is
+always run regardless of whether your test passes or fails (like a <code>finally</code>
+clause in a try-except-finally construct). Exceptions in <code>tearDown</code>
+are flagged as errors and flunk the test.
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.trial.unittest.TestCase.addCleanup.html" title="twisted.trial.unittest.TestCase.addCleanup">TestCase.addCleanup</a></code> is
+another useful tool for cleaning up. With it, you can register callables to
+clean up resources as the test allocates them. Generally, code should be
+written so that only resources allocated in the tests need to be cleaned up in
+the tests. Resources which are allocated internally by the implementation
+should be cleaned up by the implementation.</p>
+
+<p>If your code uses Deferreds or depends on the reactor running, you can
+return a Deferred from your test method, setUp, or tearDown and Trial will
+do the right thing. That is, it will run the reactor for you until the
+Deferred has triggered and its callbacks have been run. Don't use
+ <code>reactor.run()</code>, <code>reactor.stop()</code>, <code>reactor.crash()</code> or <code>reactor.iterate()</code> in your tests.</p>
+
+<p>Calls to <code>reactor.callLater</code> create <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IDelayedCall.html" title="twisted.internet.interfaces.IDelayedCall">IDelayedCall</a></code>s. These need to be run
+or cancelled during a test, otherwise they will outlive the test. This would
+be bad, because they could interfere with a later test, causing confusing
+failures in unrelated tests! For this reason, Trial checks the reactor to make
+sure there are no leftover <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IDelayedCall.html" title="twisted.internet.interfaces.IDelayedCall">IDelayedCall</a></code>s in the reactor after a
+test, and will fail the test if there are. The cleanest and simplest way to
+make sure this all works is to return a Deferred from your test.</p>
+
+<p>Similarly, sockets created during a test should be closed by the end of the
+test. This applies to both listening ports and client connections. So, calls
+to <code>reactor.listenTCP</code> (and <code>listenUNIX</code>, and so on)
+return <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IListeningPort.html" title="twisted.internet.interfaces.IListeningPort">IListeningPort</a></code>s, and these should be
+cleaned up before a test ends by calling their <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IListeningPort.stopListening.html" title="twisted.internet.interfaces.IListeningPort.stopListening">stopListening</a></code> method.
+Calls to <code>reactor.connectTCP</code> return <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IConnector.html" title="twisted.internet.interfaces.IConnector">IConnector</a></code>s, which should be cleaned
+up by calling their <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IConnector.disconnect.html" title="twisted.internet.interfaces.IConnector.disconnect">disconnect</a></code> method. Trial
+will warn about unclosed sockets.</p>
+
+<p>The golden rule is: If your tests call a function which returns a Deferred,
+your test should return a Deferred.</p>
+
+<h3>Using Timers to Detect Failing Tests<a name="auto4"/></h3>
+
+<p>It is common for tests to establish some kind of fail-safe timeout that
+will terminate the test in case something unexpected has happened and none of
+the normal test-failure paths are followed. This timeout puts an upper bound
+on the time that a test can consume, and prevents the entire test suite from
+stalling because of a single test. This is especially important for the
+Twisted test suite, because it is run automatically by the buildbot whenever
+changes are committed to the Subversion repository.</p>
+
+<p>The way to do this in Trial is to set the <code>.timeout</code> attribute
+on your unit test method. Set the attribute to the number of seconds you wish
+to elapse before the test raises a timeout error. Trial has a default timeout
+which will be applied even if the <code>timeout</code> attribute is not set.
+The Trial default timeout is usually sufficient and should be overridden only
+in unusual cases.</p>
+
+<h3>Interacting with warnings in tests<a name="auto5"/></h3>
+
+<p>Trial includes specific support for interacting with Python's
+ <code>warnings</code> module. This support allows warning-emitting code to
+be written test-driven, just as any other code would be. It also improves
+the way in which warnings reporting when a test suite is running.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.trial.unittest.TestCase.flushWarnings.html" title="twisted.trial.unittest.TestCase.flushWarnings">TestCase.flushWarnings</a></code>
+allows tests to be written which make assertions about what warnings have
+been emitted during a particular test method. In order to test a warning with
+ <code>flushWarnings</code>, write a test which first invokes the code which
+will emit a warning and then calls <code>flushWarnings</code> and makes
+assertions about the result. For example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SomeWarningsTests</span>(<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_warning</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">warnings</span>.<span class="py-src-variable">warn</span>(<span class="py-src-string">&quot;foo is bad&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">flushWarnings</span>()), <span class="py-src-number">1</span>)
+</pre>
+
+<p>Warnings emitted in tests which are not flushed will be included by the
+default reporter in its output after the result of the test. If Python's
+warnings filter system (see <a href="http://docs.python.org/using/cmdline.html#cmdoption-unittest-discover-W" shape="rect">the
+-W command option to Python</a>) is configured to treat a warning as an error,
+then unflushed warnings will causes tests to fail and will be included in
+the summary section of the default reporter. Note that unlike usual
+operation, when <code>warnings.warn</code> is called as part of a test
+method, it will not raise an exception when warnings have been configured as
+errors. However, if called outside of a test method (for example, at module
+scope in a test module or a module imported by a test module) then it
+ <em>will</em> raise an exception.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/threading.html b/doc/core/howto/threading.html
new file mode 100644
index 0000000..9eda7a4
--- /dev/null
+++ b/doc/core/howto/threading.html
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using Threads in Twisted</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using Threads in Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Running code in a thread-safe manner</a></li><li><a href="#auto1">Running code in threads</a></li><li><a href="#auto2">Utility Methods</a></li><li><a href="#auto3">Managing the Thread Pool</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Running code in a thread-safe manner<a name="auto0"/></h2>
+
+ <p>Most code in Twisted is not thread-safe. For example,
+ writing data to a transport from a protocol is not thread-safe.
+ Therefore, we want a way to schedule methods to be run in the
+ main event loop. This can be done using the function <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorThreads.callFromThread.html" title="twisted.internet.interfaces.IReactorThreads.callFromThread">twisted.internet.interfaces.IReactorThreads.callFromThread</a></code>:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">notThreadSafe</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;do something that isn't thread-safe&quot;&quot;&quot;</span>
+ <span class="py-src-comment"># ...</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">threadSafeScheduler</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Run in thread-safe manner.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callFromThread</span>(<span class="py-src-variable">notThreadSafe</span>, <span class="py-src-number">3</span>) <span class="py-src-comment"># will run 'notThreadSafe(3)'</span>
+ <span class="py-src-comment"># in the event loop</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h2>Running code in threads<a name="auto1"/></h2>
+
+ <p>Sometimes we may want to run methods in threads - for
+ example, in order to access blocking APIs. Twisted provides
+ methods for doing so using the IReactorThreads API (<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorThreads.html" title="twisted.internet.interfaces.IReactorThreads">twisted.internet.interfaces.IReactorThreads</a></code>).
+ Additional utility functions are provided in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.threads.html" title="twisted.internet.threads">twisted.internet.threads</a></code>. Basically, these
+ methods allow us to queue methods to be run by a thread
+ pool.</p>
+
+ <p>For example, to run a method in a thread we can do:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">aSillyBlockingMethod</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+ <span class="py-src-variable">time</span>.<span class="py-src-variable">sleep</span>(<span class="py-src-number">2</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">x</span>
+
+<span class="py-src-comment"># run method in thread</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callInThread</span>(<span class="py-src-variable">aSillyBlockingMethod</span>, <span class="py-src-string">&quot;2 seconds have passed&quot;</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h2>Utility Methods<a name="auto2"/></h2>
+
+ <p>The utility methods are not part of the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">twisted.internet.reactor</a></code> APIs, but are implemented
+ in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.threads.html" title="twisted.internet.threads">twisted.internet.threads</a></code>.</p>
+
+ <p>If we have multiple methods to run sequentially within a thread,
+ we can do:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">threads</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">aSillyBlockingMethodOne</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+ <span class="py-src-variable">time</span>.<span class="py-src-variable">sleep</span>(<span class="py-src-number">2</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">x</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">aSillyBlockingMethodTwo</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">x</span>
+
+<span class="py-src-comment"># run both methods sequentially in a thread</span>
+<span class="py-src-variable">commands</span> = [(<span class="py-src-variable">aSillyBlockingMethodOne</span>, [<span class="py-src-string">&quot;Calling First&quot;</span>], {})]
+<span class="py-src-variable">commands</span>.<span class="py-src-variable">append</span>((<span class="py-src-variable">aSillyBlockingMethodTwo</span>, [<span class="py-src-string">&quot;And the second&quot;</span>], {}))
+<span class="py-src-variable">threads</span>.<span class="py-src-variable">callMultipleInThread</span>(<span class="py-src-variable">commands</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>For functions whose results we wish to get, we can have the
+ result returned as a Deferred:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">threads</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">doLongCalculation</span>():
+ <span class="py-src-comment"># .... do long calculation here ...</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-number">3</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">x</span>
+
+<span class="py-src-comment"># run method in thread and get result as defer.Deferred</span>
+<span class="py-src-variable">d</span> = <span class="py-src-variable">threads</span>.<span class="py-src-variable">deferToThread</span>(<span class="py-src-variable">doLongCalculation</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>If you wish to call a method in the reactor thread and get its result,
+ you can use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.threads.blockingCallFromThread.html" title="twisted.internet.threads.blockingCallFromThread">blockingCallFromThread</a></code>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">threads</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">getPage</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">error</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Error</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">inThread</span>():
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">threads</span>.<span class="py-src-variable">blockingCallFromThread</span>(
+ <span class="py-src-variable">reactor</span>, <span class="py-src-variable">getPage</span>, <span class="py-src-string">&quot;http://twistedmatrix.com/&quot;</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">Error</span>, <span class="py-src-variable">exc</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">exc</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">result</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callFromThread</span>(<span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callInThread</span>(<span class="py-src-variable">inThread</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p><code>blockingCallFromThread</code> will return the object or raise
+ the exception returned or raised by the function passed to it. If the
+ function passed to it returns a Deferred, it will return the value the
+ Deferred is called back with or raise the exception it is errbacked
+ with.</p>
+
+ <h2>Managing the Thread Pool<a name="auto3"/></h2>
+
+ <p>The thread pool is implemented by <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.threadpool.ThreadPool.html" title="twisted.python.threadpool.ThreadPool">twisted.python.threadpool.ThreadPool</a></code>.</p>
+
+ <p>We may want to modify the size of the threadpool, increasing
+ or decreasing the number of threads in use. We can do this
+ do this quite easily:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">suggestThreadPoolSize</span>(<span class="py-src-number">30</span>)
+</pre>
+
+ <p>The default size of the thread pool depends on the reactor being used;
+ the default reactor uses a minimum size of 5 and a maximum size of 10. Be
+ careful that you understand threads and their resource usage before
+ drastically altering the thread pool sizes.</p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/time.html b/doc/core/howto/time.html
new file mode 100644
index 0000000..010f296
--- /dev/null
+++ b/doc/core/howto/time.html
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Scheduling tasks for the future</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Scheduling tasks for the future</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <p>Let's say we want to run a task X seconds in the future.
+ The way to do that is defined in the reactor interface <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTime.html" title="twisted.internet.interfaces.IReactorTime">twisted.internet.interfaces.IReactorTime</a></code>:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">f</span>(<span class="py-src-parameter">s</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;this will run 3.5 seconds after it was scheduled: %s&quot;</span> % <span class="py-src-variable">s</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">3.5</span>, <span class="py-src-variable">f</span>, <span class="py-src-string">&quot;hello, world&quot;</span>)
+
+<span class="py-src-comment"># f() will only be called if the event loop is started.</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>If the result of the function is important or if it may be necessary
+ to handle exceptions it raises, then the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.task.deferLater.html" title="twisted.internet.task.deferLater">twisted.internet.task.deferLater</a></code> utility conveniently
+ takes care of creating a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> and setting up a delayed
+ call:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">task</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">f</span>(<span class="py-src-parameter">s</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;This will run 3.5 seconds after it was scheduled: %s&quot;</span> % <span class="py-src-variable">s</span>
+
+<span class="py-src-variable">d</span> = <span class="py-src-variable">task</span>.<span class="py-src-variable">deferLater</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">3.5</span>, <span class="py-src-variable">f</span>, <span class="py-src-string">&quot;hello, world&quot;</span>)
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">called</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">result</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">called</span>)
+
+<span class="py-src-comment"># f() will only be called if the event loop is started.</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>If we want a task to run every X seconds repeatedly, we can
+ use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.task.LoopingCall.html" title="twisted.internet.task.LoopingCall">twisted.internet.task.LoopingCall</a></code>:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">task</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">runEverySecond</span>():
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;a second has passed&quot;</span>
+
+<span class="py-src-variable">l</span> = <span class="py-src-variable">task</span>.<span class="py-src-variable">LoopingCall</span>(<span class="py-src-variable">runEverySecond</span>)
+<span class="py-src-variable">l</span>.<span class="py-src-variable">start</span>(<span class="py-src-number">1.0</span>) <span class="py-src-comment"># call every second</span>
+
+<span class="py-src-comment"># l.stop() will stop the looping calls</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>If we want to cancel a task that we've scheduled:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">f</span>():
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I'll never run.&quot;</span>
+
+<span class="py-src-variable">callID</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">f</span>)
+<span class="py-src-variable">callID</span>.<span class="py-src-variable">cancel</span>()
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>As with all reactor-based code, in order for scheduling to work the reactor must be started using <code class="python">reactor.run()</code>.</p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/trial.html b/doc/core/howto/trial.html
new file mode 100644
index 0000000..e52d0c0
--- /dev/null
+++ b/doc/core/howto/trial.html
@@ -0,0 +1,2042 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Test-driven development with Twisted</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Test-driven development with Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introductory example of Python unit testing</a></li><li><a href="#auto1">Creating an API and writing tests</a></li><li><a href="#auto2">Making the tests pass</a></li><ul><li><a href="#auto3">Factoring out common test logic</a></li></ul><li><a href="#auto4">Twisted specific testing</a></li><li><a href="#auto5">Testing a protocol</a></li><ul><li><a href="#auto6">Creating and testing the server</a></li><li><a href="#auto7">Creating and testing the client</a></li></ul><li><a href="#auto8">More good practices</a></li><ul><li><a href="#auto9">Testing scheduling</a></li><li><a href="#auto10">Cleaning up after tests</a></li><li><a href="#auto11">Handling logged errors</a></li></ul><li><a href="#auto12">Resolve a bug</a></li><li><a href="#auto13">Code coverage</a></li><li><a href="#auto14">Conclusion</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<p>Writing good code is hard, or at least it can be. A major challenge is
+to ensure that your code remains correct as you add new functionality.</p>
+
+<p><a href="http://en.wikipedia.org/wiki/Unit_test" shape="rect">Unit testing</a> is a
+modern, light-weight testing methodology in widespread use in many
+programming languages. Development that relies on unit tests is often
+referred to as Test-Driven Development
+(<a href="http://en.wikipedia.org/wiki/Test-driven_development" shape="rect">TDD</a>).
+Most Twisted code is tested using TDD.</p>
+
+<p>To gain a solid understanding of unit testing in Python, you should read
+the <a href="http://docs.python.org/library/unittest.html" shape="rect">unittest --
+Unit testing framework chapter</a> of the <a href="http://docs.python.org/library/index.html" shape="rect">Python Library
+Reference</a>. There is also a ton of information available online and in
+books.</p>
+
+<h2>Introductory example of Python unit testing<a name="auto0"/></h2>
+
+<p>This document is principally a guide to Trial, Twisted's unit testing
+framework. Trial is based on Python's unit testing framework. While we do not
+aim to give a comprehensive guide to general Python unit testing, it will be
+helpful to consider a simple non-networked example before expanding to cover a
+networking code that requires the special capabilities of Trial. If you are
+already familiar with unit test in Python, jump straight to the section
+specific to <a href="#twisted" shape="rect">testing Twisted code</a>.</p>
+
+<p><div class="note"><strong>Note: </strong>In what follows we will make a series of refinements
+to some simple classes. In order to keep the examples and source code links
+complete and to allow you to run Trial on the intermediate results at every
+stage, I add <code>_N</code> (where the <code>N</code> are successive
+integers) to file names to keep them separate. This is a minor visual
+distraction that should be ignored.</div></p>
+
+<h2>Creating an API and writing tests<a name="auto1"/></h2>
+
+<p>We'll create a library for arithmetic calculation. First, create a
+project structure with a directory called <code class="shell">calculus</code> containing an empty <code class="py-filename">__init__.py</code> file.</p>
+
+<p>Then put the following simple class definition API into <code class="py-filename">calculus/base_1.py</code>:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-comment"># -*- test-case-name: calculus.test.test_base_1 -*-</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Calculation</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">pass</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">subtract</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">pass</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">multiply</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">pass</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">divide</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">pass</span>
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/base_1.py"><span class="filename">listings/trial/calculus/base_1.py</span></a></div></div>
+
+<p>(Ignore the <code class="python">test-case-name</code> comment for
+now. You'll see why that's useful <a href="#comment" shape="rect">below</a>.)</p>
+
+<p>We've written the interface, but not the code. Now we'll write a set of
+tests. At this point of development, we'll be expecting all tests to
+fail. Don't worry, that's part of the point. Once we have a test framework
+functioning, and we have some decent tests written (and failing!), we'll go
+and do the actual development of our calculation API. This is the preferred
+way to work for many people using TDD - write tests first, make sure they
+fail, then do development. Others are not so strict and write tests after
+doing the development.</p>
+
+<p>Create a <code class="shell">test</code> directory beneath <code class="shell">calculus</code>, with an empty <code class="py-filename">__init__.py</code> file. In a <code class="py-filename">calculus/test/test_base_1.py</code>, put the
+following:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">base_1</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Calculation</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unittest</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CalculationTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_add</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">calc</span>.<span class="py-src-variable">add</span>(<span class="py-src-number">3</span>, <span class="py-src-number">8</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">result</span>, <span class="py-src-number">11</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_subtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">calc</span>.<span class="py-src-variable">subtract</span>(<span class="py-src-number">7</span>, <span class="py-src-number">3</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">result</span>, <span class="py-src-number">4</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_multiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">calc</span>.<span class="py-src-variable">multiply</span>(<span class="py-src-number">12</span>, <span class="py-src-number">5</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">result</span>, <span class="py-src-number">60</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_divide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">calc</span>.<span class="py-src-variable">divide</span>(<span class="py-src-number">12</span>, <span class="py-src-number">5</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">result</span>, <span class="py-src-number">2</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/test/test_base_1.py"><span class="filename">listings/trial/calculus/test/test_base_1.py</span></a></div></div>
+
+<p>You should now have the following 4 files:
+
+<pre class="shell" xml:space="preserve">
+ calculus/__init__.py
+ calculus/base_1.py
+ calculus/test/__init__.py
+ calculus/test/test_base_1.py
+</pre>
+</p>
+
+<p>To run the tests, there are two things you must get set up. Make sure
+you get these both done - nothing below will work unless you do.</p>
+
+<p>First, make sure that the directory that <em>contains</em> your
+ <code class="shell">calculus</code> directory is in your Python load path. If you're
+using the Bash shell on some form of unix (e.g., Linux, Mac OS X), run
+ <code class="shell">PYTHONPATH=&quot;$PYTHONPATH:`pwd`/..&quot;</code> at
+the command line in the <code class="shell">calculus</code> directory. Once you have your
+Python path set up correctly, you should be able to run Python from the
+command line and <code class="python">import calculus</code> without seeing
+an import error.</p>
+
+<p>Second, make sure you can run the <code class="shell">trial</code>
+command. That is, make sure the directory containing the <code class="shell">trial</code>
+program on you system is in your shell's <code class="shell">PATH</code>. The easiest way to check if you have this is to
+try running <code class="shell">trial --help</code> at the command line. If
+you see a list of invocation options, you're in business. If your shell
+reports something like <code class="shell">trial: command not found</code>,
+make sure you have Twisted installed properly, and that the Twisted
+ <code class="shell">bin</code> directory is in your <code class="shell">PATH</code>. If
+you don't know how to do this, get some local help, or figure it out by
+searching online for information on setting and changing environment
+variables for you operating system.</p>
+
+<p>With those (one-time) preliminary steps out of the way, let's perform
+the tests. Run <code class="shell">trial calculus.test.test_base_1</code> from the
+command line from the <code class="shell">calculus</code> directory.
+
+You should see the following output (though your files are probably not in
+ <code class="shell">/tmp</code>:</p>
+
+<pre class="shell" xml:space="preserve">
+$ trial calculus.test.test_base_1
+calculus.test.test_base_1
+ CalculationTestCase
+ test_add ... [FAIL]
+ test_divide ... [FAIL]
+ test_multiply ... [FAIL]
+ test_subtract ... [FAIL]
+
+===============================================================================
+[FAIL]
+Traceback (most recent call last):
+ File &quot;/tmp/calculus/test/test_base_1.py&quot;, line 8, in test_add
+ self.assertEqual(result, 11)
+twisted.trial.unittest.FailTest: not equal:
+a = None
+b = 11
+
+
+calculus.test.test_base_1.CalculationTestCase.test_add
+===============================================================================
+[FAIL]
+Traceback (most recent call last):
+ File &quot;/tmp/calculus/test/test_base_1.py&quot;, line 23, in test_divide
+ self.assertEqual(result, 2)
+twisted.trial.unittest.FailTest: not equal:
+a = None
+b = 2
+
+
+calculus.test.test_base_1.CalculationTestCase.test_divide
+===============================================================================
+[FAIL]
+Traceback (most recent call last):
+ File &quot;/tmp/calculus/test/test_base_1.py&quot;, line 18, in test_multiply
+ self.assertEqual(result, 60)
+twisted.trial.unittest.FailTest: not equal:
+a = None
+b = 60
+
+
+calculus.test.test_base_1.CalculationTestCase.test_multiply
+===============================================================================
+[FAIL]
+Traceback (most recent call last):
+ File &quot;/tmp/calculus/test/test_base_1.py&quot;, line 13, in test_subtract
+ self.assertEqual(result, 4)
+twisted.trial.unittest.FailTest: not equal:
+a = None
+b = 4
+
+
+calculus.test.test_base_1.CalculationTestCase.test_subtract
+-------------------------------------------------------------------------------
+Ran 4 tests in 0.042s
+
+FAILED (failures=4)
+</pre>
+
+<p>How to interpret this output? You get a list of the individual tests, each
+followed by its result. By default, failures are printed at the end, but this
+can be changed with the <code class="shell">-e</code> (or <code class="shell">--rterrors</code>) option.</p>
+
+<p>One very useful thing in this output is the fully-qualified name of the
+failed tests. This appears at the bottom of each =-delimited area of the
+output. This allows you to copy and paste it to just run a single test you're
+interested in. In our example, you could run <code class="shell">trial
+calculus.test.test_base_1.CalculationTestCase.test_subtract</code> from the
+shell.</p>
+
+<p>Note that trial can use different reporters to modify its output. Run
+ <code class="shell">trial --help-reporters</code> to see a list of
+reporters.</p>
+
+<p>
+The tests can be run by <code class="shell">trial</code> in multiple ways:
+<ul>
+ <li><code class="shell">trial calculus</code>: run all the tests for the
+ calculus package.</li>
+
+ <li><code class="shell">trial calculus.test</code>: run using Python's
+ <code class="python">import</code> notation.</li>
+
+ <li><code class="shell">trial calculus.test.test_base_1</code>: as above, for
+ a specific test module. You can follow that logic by putting your class name
+ and even a method name to only run those specific tests.</li>
+
+ <li><a name="comment" shape="rect"/><code class="shell">trial
+ --testmodule=calculus/base_1.py</code>: use the <code class="python">test-case-name</code> comment in the first line of
+ <code class="py-filename">calculus/base_1.py</code> to find the tests.</li>
+
+ <li><code class="shell">trial calculus/test</code>: run all the tests in the
+ test directory (not recommended).</li>
+
+ <li><code class="shell">trial calculus/test/test_base_1.py</code>: run a
+ specific test file (not recommended).</li>
+</ul>
+
+The first 3 versions using full qualified names are strongly encouraged: they
+are much more reliable and they allow you to easily be more selective in your
+test runs.
+</p>
+
+<p>You'll notice that Trial create a <code class="shell">_trial_temp</code> directory in
+the directory where you run the tests. This has a file called
+ <code class="shell">test.log</code> which contains the log output of the tests (created
+using <code class="python">log.msg</code> or <code class="python">log.err</code> functions). Examine this file if you add
+logging to your tests.</p>
+
+<h2>Making the tests pass<a name="auto2"/></h2>
+
+<p>Now that we have a working test framework in place, and our tests are
+failing (as expected) we can go and try to implement the correct API. We'll do
+that in a new version of the above base_1
+module, <code class="py-filename">calculus/base_2.py</code>:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-comment"># -*- test-case-name: calculus.test.test_base_2 -*-</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Calculation</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> + <span class="py-src-variable">b</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">subtract</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> - <span class="py-src-variable">b</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">multiply</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> * <span class="py-src-variable">b</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">divide</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> / <span class="py-src-variable">b</span>
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/base_2.py"><span class="filename">listings/trial/calculus/base_2.py</span></a></div></div>
+
+<p>We'll also create a new version of test_base_1 which imports and tests this
+new implementation,
+in <code class="py-filename">calculus/test_base_2.py</code>:</p>
+
+<p><div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">base_2</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Calculation</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unittest</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CalculationTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_add</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">calc</span>.<span class="py-src-variable">add</span>(<span class="py-src-number">3</span>, <span class="py-src-number">8</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">result</span>, <span class="py-src-number">11</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_subtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">calc</span>.<span class="py-src-variable">subtract</span>(<span class="py-src-number">7</span>, <span class="py-src-number">3</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">result</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_multiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">calc</span>.<span class="py-src-variable">multiply</span>(<span class="py-src-number">12</span>, <span class="py-src-number">5</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">result</span>, <span class="py-src-number">60</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_divide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">calc</span>.<span class="py-src-variable">divide</span>(<span class="py-src-number">12</span>, <span class="py-src-number">5</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">result</span>, <span class="py-src-number">2</span>)
+</pre><div class="caption">test_base_2 - <a href="listings/trial/calculus/test/test_base_2.py"><span class="filename">listings/trial/calculus/test/test_base_2.py</span></a></div></div> is a copy of test_base_1, but with the import changed. Run <code class="shell">trial</code> again as above, and your tests should now pass:</p>
+
+<pre class="shell" xml:space="preserve">
+$ trial calculus.test.test_base_2
+
+Running 4 tests.
+calculus.test.test_base
+ CalculationTestCase
+ test_add ... [OK]
+ test_divide ... [OK]
+ test_multiply ... [OK]
+ test_subtract ... [OK]
+
+-------------------------------------------------------------------------------
+Ran 4 tests in 0.067s
+
+PASSED (successes=4)
+</pre>
+
+<h3>Factoring out common test logic<a name="auto3"/></h3>
+
+<p>You'll notice that our test file contains redundant code. Let's get rid
+of that. Python's unit testing framework allows your test class to define a
+ <code class="python">setUp</code> method that is called before
+ <em>each</em> test method in the class. This allows you to add attributes
+to <code class="python">self</code> that can be used in tests
+methods. We'll also add a parameterized test method to further simplify the
+code.</p>
+
+<p>Note that a test class may also provide the counterpart of <code class="python">setUp</code>, named <code class="python">tearDown</code>,
+which will be called after <em>each</em> test (whether successful or
+not). <code class="python">tearDown</code> is mainly used for post-test
+cleanup purposes. We will not use <code class="python">tearDown</code>
+until later.</p>
+
+<p>Create <code class="py-filename">calculus/test/test_base_2b.py</code> as
+follows:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">base_2</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Calculation</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unittest</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CalculationTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUp</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_test</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">operation</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>, <span class="py-src-parameter">expected</span>):
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">operation</span>(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">expected</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_add</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">add</span>, <span class="py-src-number">3</span>, <span class="py-src-number">8</span>, <span class="py-src-number">11</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_subtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">subtract</span>, <span class="py-src-number">7</span>, <span class="py-src-number">3</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_multiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">multiply</span>, <span class="py-src-number">6</span>, <span class="py-src-number">9</span>, <span class="py-src-number">54</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_divide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">divide</span>, <span class="py-src-number">12</span>, <span class="py-src-number">5</span>, <span class="py-src-number">2</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/test/test_base_2b.py"><span class="filename">listings/trial/calculus/test/test_base_2b.py</span></a></div></div>
+
+<p>Much cleaner, no?</p>
+
+<p>We'll now add some additional error tests. Testing just for successful
+use of the API is generally not enough, especially if you expect your code
+to be used by others. Let's make sure the <code class="python">Calculation</code> class raises exceptions if someone tries
+to call its methods with arguments that cannot be converted to
+integers.</p>
+
+<p>We arrive at <code class="py-filename">calculus/test/test_base_3.py</code>:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">base_3</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Calculation</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unittest</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CalculationTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUp</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_test</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">operation</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>, <span class="py-src-parameter">expected</span>):
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">operation</span>(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">expected</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_test_error</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">operation</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertRaises</span>(<span class="py-src-variable">TypeError</span>, <span class="py-src-variable">operation</span>, <span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-number">2</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertRaises</span>(<span class="py-src-variable">TypeError</span>, <span class="py-src-variable">operation</span>, <span class="py-src-string">&quot;bar&quot;</span>, <span class="py-src-string">&quot;egg&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertRaises</span>(<span class="py-src-variable">TypeError</span>, <span class="py-src-variable">operation</span>, [<span class="py-src-number">3</span>], [<span class="py-src-number">8</span>, <span class="py-src-number">2</span>])
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertRaises</span>(<span class="py-src-variable">TypeError</span>, <span class="py-src-variable">operation</span>, {<span class="py-src-string">&quot;e&quot;</span>: <span class="py-src-number">3</span>}, {<span class="py-src-string">&quot;r&quot;</span>: <span class="py-src-string">&quot;t&quot;</span>})
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_add</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">add</span>, <span class="py-src-number">3</span>, <span class="py-src-number">8</span>, <span class="py-src-number">11</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_subtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">subtract</span>, <span class="py-src-number">7</span>, <span class="py-src-number">3</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_multiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">multiply</span>, <span class="py-src-number">6</span>, <span class="py-src-number">9</span>, <span class="py-src-number">54</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_divide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">divide</span>, <span class="py-src-number">12</span>, <span class="py-src-number">5</span>, <span class="py-src-number">2</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_errorAdd</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test_error</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">add</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_errorSubtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test_error</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">subtract</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_errorMultiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test_error</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">multiply</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_errorDivide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_test_error</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>.<span class="py-src-variable">divide</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/test/test_base_3.py"><span class="filename">listings/trial/calculus/test/test_base_3.py</span></a></div></div>
+
+<p>We've added four new tests and one general-purpose function, <code class="python">_test_error</code>. This function uses the <code class="python">assertRaises</code> method, which takes an exception class,
+a function to run and its arguments, and checks that calling the function
+on the arguments does indeed raise the given exception.</p>
+
+<p>If you run the above, you'll see that not all tests fail. In Python it's
+often valid to add and multiply objects of different and even differing
+types, so the code in the add and mutiply tests does not raise an exception
+and those tests therefore fail. So let's add explicit type conversion to
+our API class. This brings us to <code class="py-filename">calculus/base_3.py</code>:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+</p><span class="py-src-comment"># -*- test-case-name: calculus.test.test_base_3 -*-</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Calculation</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_make_ints</span>(<span class="py-src-parameter">self</span>, *<span class="py-src-parameter">args</span>):
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">map</span>(<span class="py-src-variable">int</span>, <span class="py-src-variable">args</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">ValueError</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">TypeError</span>(<span class="py-src-string">&quot;Couldn't coerce arguments to integers: %s&quot;</span> % <span class="py-src-variable">args</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">_make_ints</span>(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> + <span class="py-src-variable">b</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">subtract</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">_make_ints</span>(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> - <span class="py-src-variable">b</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">multiply</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">_make_ints</span>(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> * <span class="py-src-variable">b</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">divide</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">_make_ints</span>(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> / <span class="py-src-variable">b</span>
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/base_3.py"><span class="filename">listings/trial/calculus/base_3.py</span></a></div></div>
+
+<p>Here the <code class="python">_make_ints</code> helper function tries to
+convert a list into a list of equivalent integers, and raises a <code class="python">TypeError</code> in case the conversion goes wrong.
+
+<div class="note"><strong>Note: </strong>The <code class="python">int</code> conversion can also
+raise a <code class="python">TypeError</code> if passed something of the
+wrong type, such as a list. We'll just let that exception go by as <code class="python">TypeError</code> is already what we want in case something
+goes wrong.</div></p>
+
+
+<a name="twisted" shape="rect"/>
+<h2>Twisted specific testing<a name="auto4"/></h2>
+
+<p>Up to this point we've been doing fairly standard Python unit testing.
+With only a few cosmetic changes (most importantly, directly importing
+ <code class="python">unittest</code> instead of using Twisted's <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.trial.unittest.html" title="twisted.trial.unittest">unittest</a></code> version) we could make the
+above tests run using Python's standard library unit testing framework.</p>
+
+<p>Here we will assume a basic familiarity with Twisted's network I/O, timing,
+and Deferred APIs. If you haven't already read them, you should read the
+documentation on <a href="servers.html" shape="rect">Writing
+Servers</a>, <a href="clients.html" shape="rect">Writing Clients</a>,
+and <a href="defer.html" shape="rect">Deferreds</a>.</p>
+
+<p>Now we'll get to the real point of this tutorial and take advantage of
+Trial to test Twisted code.</p>
+
+<h2>Testing a protocol<a name="auto5"/></h2>
+
+<p>We'll now create a custom protocol to invoke our class from within a
+telnet-like session. We'll remotely call commands with arguments and read back
+the response. The goal will be to test our network code without creating
+sockets.</p>
+
+<h3>Creating and testing the server<a name="auto6"/></h3>
+
+<p>First we'll write the tests, and then explain what they do. The first
+version of the remote test code is:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">remote_1</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">RemoteCalculationFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unittest</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">test</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">proto_helpers</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteCalculationTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUp</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">RemoteCalculationFactory</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">buildProtocol</span>((<span class="py-src-string">'127.0.0.1'</span>, <span class="py-src-number">0</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span> = <span class="py-src-variable">proto_helpers</span>.<span class="py-src-variable">StringTransport</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">makeConnection</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_test</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">operation</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>, <span class="py-src-parameter">expected</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">dataReceived</span>(<span class="py-src-string">'%s %d %d\r\n'</span> % (<span class="py-src-variable">operation</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">value</span>()), <span class="py-src-variable">expected</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_add</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'add'</span>, <span class="py-src-number">7</span>, <span class="py-src-number">6</span>, <span class="py-src-number">13</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_subtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'subtract'</span>, <span class="py-src-number">82</span>, <span class="py-src-number">78</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_multiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'multiply'</span>, <span class="py-src-number">2</span>, <span class="py-src-number">8</span>, <span class="py-src-number">16</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_divide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'divide'</span>, <span class="py-src-number">14</span>, <span class="py-src-number">3</span>, <span class="py-src-number">4</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/test/test_remote_1.py"><span class="filename">listings/trial/calculus/test/test_remote_1.py</span></a></div></div>
+
+<p>To fully understand this client, it helps a lot to be comfortable with
+the Factory/Protocol/Transport pattern used in Twisted.</p>
+
+<p>We first create a protocol factory object. Note that we have yet to see
+the <code class="python">RemoteCalculationFactory</code> class. It is in
+ <code class="py-filename">calculus/remote_1.py</code> below. We
+call <code class="python">buildProtocol</code> to ask the factory to build us a
+protocol object that knows how to talk to our server. We then make a fake
+network transport, an instance of <code class="python">twisted.test.proto_helpers.StringTransport</code>
+class (note that test packages are generally not part of Twisted's public API;
+<code class="python">twisted.test.proto_helpers</code> is an exception). This fake
+transport is the key to the communications. It is used to emulate a network
+connection without a network. The address and port passed to <code>buildProtocol</code>
+are typically used by the factory to choose to immediately deny remote connections; since we're using a fake transport, we can choose any value that will be acceptable to the factory. In this case the factory just ignores the address, so we don't need to pick anything in particular.</p>
+
+<p>Testing protocols without the use of real network connections is both simple and recommended when testing Twisted
+code. Even though there are many tests in Twisted that use the network,
+most good tests don't. The problem with unit tests and networking is that
+networks aren't reliable. We cannot know that they will exhibit reasonable
+behavior all the time. This creates intermittent test failures due to
+network vagaries. Right now we're trying to test our Twisted code, not
+network reliability. By setting up and using a fake transport, we can
+write 100% reliable tests. We can also test network failures in a deterministic manner, another important part of your complete test suite.</p>
+
+<p>The final key to understanding this client code is the <code class="python">_test</code> method. The call to <code class="python">dataReceived</code> simulates data arriving on the network
+transport. But where does it arrive? It's handed to the <code class="python">lineReceived</code> method of the protocol instance (in
+ <code class="py-filename">calculus/remote_1.py</code> below). So the client
+is essentially tricking the server into thinking it has received the
+operation and the arguments over the network. The server (once again, see
+below) hands the work off to its <code class="python">CalculationProxy</code> object which in turn hands it to its
+ <code class="python">Calculation</code> instance. The result is written
+back via <code class="python">sendLine</code> (into the fake string
+transport object), and is then immediately available to the client, who
+fetches it with <code class="python">tr.value()</code> and checks that it
+has the expected value. So there's quite a lot going on behind the scenes
+in the two-line <code class="python">_test</code> method above.</p>
+
+<p><em>Finally</em>, let's see the implementation of this protocol. Put the
+following into <code class="py-filename">calculus/remote_1.py</code>:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+</p><span class="py-src-comment"># -*- test-case-name: calculus.test.test_remote_1 -*-</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">base_3</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Calculation</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CalculationProxy</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">m</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">'add'</span>, <span class="py-src-string">'subtract'</span>, <span class="py-src-string">'multiply'</span>, <span class="py-src-string">'divide'</span>]:
+ <span class="py-src-variable">setattr</span>(<span class="py-src-variable">self</span>, <span class="py-src-string">'remote_%s'</span> % <span class="py-src-variable">m</span>, <span class="py-src-variable">getattr</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>, <span class="py-src-variable">m</span>))
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteCalculationProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proxy</span> = <span class="py-src-variable">CalculationProxy</span>()
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">op</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>()
+ <span class="py-src-variable">a</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">a</span>)
+ <span class="py-src-variable">b</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">b</span>)
+ <span class="py-src-variable">op</span> = <span class="py-src-variable">getattr</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">proxy</span>, <span class="py-src-string">'remote_%s'</span> % (<span class="py-src-variable">op</span>,))
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">op</span>(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">str</span>(<span class="py-src-variable">result</span>))
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteCalculationFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">Factory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">RemoteCalculationProtocol</span>
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">startLogging</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">stdout</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">RemoteCalculationFactory</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/remote_1.py"><span class="filename">listings/trial/calculus/remote_1.py</span></a></div></div>
+
+<p>As mentioned, this server creates a protocol that inherits from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.protocols.basic.LineReceiver.html" title="twisted.protocols.basic.LineReceiver">basic.LineReceiver</a></code>, and then a
+factory that uses it as protocol. The only trick is the <code class="python">CalculationProxy</code> object, which calls <code class="python">Calculation</code> methods through <code class="python">remote_*</code> methods. This pattern is used frequently in
+Twisted, because it is very explicit about what methods you are making
+accessible.</p>
+
+<p>If you run this test (<code class="shell">trial
+calculus.test.test_remote_1</code>), everything should be fine. You can also
+run a server to test it with a telnet client. To do that, call <code class="shell">python calculus/remote_1.py</code>. You should have the following output:</p>
+
+<pre class="shell" xml:space="preserve">
+2008-04-25 10:53:27+0200 [-] Log opened.
+2008-04-25 10:53:27+0200 [-] __main__.RemoteCalculationFactory starting on 46194
+2008-04-25 10:53:27+0200 [-] Starting factory &lt;__main__.RemoteCalculationFactory instance at 0x846a0cc&gt;
+</pre>
+
+<p>46194 is replaced by a random port. You can then call telnet on it:</p>
+<pre xml:space="preserve">
+$ telnet localhost 46194
+Trying 127.0.0.1...
+Connected to localhost.
+Escape character is '^]'.
+add 4123 9423
+13546
+</pre>
+
+<p>It works!</p>
+
+<h3>Creating and testing the client<a name="auto7"/></h3>
+
+<p>Of course, what we build is not particulary useful for now : we'll now build
+a client to our server, to be able to use it inside a Python program. And it
+will serve our next purpose.</p>
+
+<p>Create <code class="py-filename">calculus/test/test_client_1.py</code>:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">client_1</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">RemoteCalculationClient</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unittest</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">test</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">proto_helpers</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientCalculationTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUp</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span> = <span class="py-src-variable">proto_helpers</span>.<span class="py-src-variable">StringTransport</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span> = <span class="py-src-variable">RemoteCalculationClient</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">makeConnection</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_test</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">operation</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>, <span class="py-src-parameter">expected</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">getattr</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>, <span class="py-src-variable">operation</span>)(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">value</span>(), <span class="py-src-string">'%s %d %d\r\n'</span> % (<span class="py-src-variable">operation</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>, <span class="py-src-variable">expected</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">dataReceived</span>(<span class="py-src-string">&quot;%d\r\n&quot;</span> % (<span class="py-src-variable">expected</span>,))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_add</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'add'</span>, <span class="py-src-number">7</span>, <span class="py-src-number">6</span>, <span class="py-src-number">13</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_subtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'subtract'</span>, <span class="py-src-number">82</span>, <span class="py-src-number">78</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_multiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'multiply'</span>, <span class="py-src-number">2</span>, <span class="py-src-number">8</span>, <span class="py-src-number">16</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_divide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'divide'</span>, <span class="py-src-number">14</span>, <span class="py-src-number">3</span>, <span class="py-src-number">4</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/test/test_client_1.py"><span class="filename">listings/trial/calculus/test/test_client_1.py</span></a></div></div>
+
+<p>It's really symmetric to the server test cases. The only tricky part is
+that we don't use a client factory. We're lazy, and it's not very useful in
+the client part, so we instantiate the protocol directly.</p>
+
+<p>Incidentally, we have introduced a very important concept here: the tests
+now return a Deferred object, and the assertion is done in a callback. The
+important thing to do here is to <strong>not forget to return the
+Deferred</strong>. If you do, your tests will pass even if nothing is asserted.
+That's also why it's important to make tests fail first: if your tests pass
+whereas you know they shouldn't, there is a problem in your tests.</p>
+
+<p>We'll now add the remote client class to produce <code class="py-filename">calculus/client_1.py</code>:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+</p><span class="py-src-comment"># -*- test-case-name: calculus.test.test_client_1 -*-</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteCalculationClient</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">results</span> = []
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">results</span>.<span class="py-src-variable">pop</span>(<span class="py-src-number">0</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">line</span>))
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_sendOperation</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">op</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">results</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">d</span>)
+ <span class="py-src-variable">line</span> = <span class="py-src-string">&quot;%s %d %d&quot;</span> % (<span class="py-src-variable">op</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">line</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;add&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">subtract</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;subtract&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">multiply</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;multiply&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">divide</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;divide&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/client_1.py"><span class="filename">listings/trial/calculus/client_1.py</span></a></div></div>
+
+
+<h2>More good practices<a name="auto8"/></h2>
+
+<h3>Testing scheduling<a name="auto9"/></h3>
+
+<p>When testing code that involves the passage of time, waiting e.g. for a two hour timeout to occur in a test is not very realistic. Twisted provides a solution to this, the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.task.Clock.html" title="twisted.internet.task.Clock">Clock</a></code> class that allows one to simulate the passage of time.</p>
+
+<p>As an example we'll test the code for client request timeout: since our client
+uses TCP it can hang for a long time (firewall, connectivity problems, etc...).
+So generally we need to implement timeouts on the client side. Basically it's
+just that we send a request, don't receive a response and expect a timeout error
+to be triggered after a certain duration.
+</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">client_2</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">RemoteCalculationClient</span>, <span class="py-src-variable">ClientTimeoutError</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">task</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unittest</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">test</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">proto_helpers</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientCalculationTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUp</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span> = <span class="py-src-variable">proto_helpers</span>.<span class="py-src-variable">StringTransportWithDisconnection</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clock</span> = <span class="py-src-variable">task</span>.<span class="py-src-variable">Clock</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span> = <span class="py-src-variable">RemoteCalculationClient</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">callLater</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">clock</span>.<span class="py-src-variable">callLater</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">makeConnection</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_test</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">operation</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>, <span class="py-src-parameter">expected</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">getattr</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>, <span class="py-src-variable">operation</span>)(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">value</span>(), <span class="py-src-string">'%s %d %d\r\n'</span> % (<span class="py-src-variable">operation</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>, <span class="py-src-variable">expected</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">dataReceived</span>(<span class="py-src-string">&quot;%d\r\n&quot;</span> % (<span class="py-src-variable">expected</span>,))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_add</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'add'</span>, <span class="py-src-number">7</span>, <span class="py-src-number">6</span>, <span class="py-src-number">13</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_subtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'subtract'</span>, <span class="py-src-number">82</span>, <span class="py-src-number">78</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_multiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'multiply'</span>, <span class="py-src-number">2</span>, <span class="py-src-number">8</span>, <span class="py-src-number">16</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_divide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'divide'</span>, <span class="py-src-number">14</span>, <span class="py-src-number">3</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_timeout</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">add</span>(<span class="py-src-number">9</span>, <span class="py-src-number">4</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">value</span>(), <span class="py-src-string">'add 9 4\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clock</span>.<span class="py-src-variable">advance</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">timeOut</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">assertFailure</span>(<span class="py-src-variable">d</span>, <span class="py-src-variable">ClientTimeoutError</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/test/test_client_2.py"><span class="filename">listings/trial/calculus/test/test_client_2.py</span></a></div></div>
+
+<p>What happens here? We instantiate our protocol as usual, the only trick
+is to create the clock, and assign <code class="python">proto.callLater</code> to
+ <code class="python">clock.callLater</code>. Thus, every callLater calls in the protocol
+will finish before <code class="python">clock.advance()</code> returns.</p>
+
+<p>In the new test (test_timeout), we call <code class="python">clock.advance</code>, that simulates and advance in time
+(logically it's similar to a <code class="python">time.sleep</code> call). And
+we just have to verify that our Deferred got a timeout error.</p>
+
+<p>Let's implement that in our code.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+</p><span class="py-src-comment"># -*- test-case-name: calculus.test.test_client_2 -*-</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>, <span class="py-src-variable">reactor</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientTimeoutError</span>(<span class="py-src-parameter">Exception</span>):
+ <span class="py-src-keyword">pass</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteCalculationClient</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-variable">callLater</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>
+ <span class="py-src-variable">timeOut</span> = <span class="py-src-number">60</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">results</span> = []
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">d</span>, <span class="py-src-variable">callID</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">results</span>.<span class="py-src-variable">pop</span>(<span class="py-src-number">0</span>)
+ <span class="py-src-variable">callID</span>.<span class="py-src-variable">cancel</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">line</span>))
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cancel</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">d</span>):
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">ClientTimeoutError</span>())
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_sendOperation</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">op</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">callID</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">timeOut</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_cancel</span>, <span class="py-src-variable">d</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">results</span>.<span class="py-src-variable">append</span>((<span class="py-src-variable">d</span>, <span class="py-src-variable">callID</span>))
+ <span class="py-src-variable">line</span> = <span class="py-src-string">&quot;%s %d %d&quot;</span> % (<span class="py-src-variable">op</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">line</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;add&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">subtract</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;subtract&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">multiply</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;multiply&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">divide</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;divide&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/client_2.py"><span class="filename">listings/trial/calculus/client_2.py</span></a></div></div>
+
+<p>The only important thing here is to not forget to cancel our callLater
+when everything went fine.</p>
+
+<h3>Cleaning up after tests<a name="auto10"/></h3>
+
+<p>This chapter is mainly intended for people that want to have sockets or
+processes created in their tests. If it's still not obvious, you must try to
+avoid that like the plague, because it ends up with a lot of problems, one of
+them being intermittent failures. And intermittent failures are the plague
+of automated tests.</p>
+
+<p>To actually test that, we'll launch a server with our protocol.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">remote_1</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">RemoteCalculationFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">client_2</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">RemoteCalculationClient</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unittest</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">protocol</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteRunCalculationTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUp</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">RemoteCalculationFactory</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">port</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">factory</span>, <span class="py-src-variable">interface</span>=<span class="py-src-string">&quot;127.0.0.1&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">client</span> = <span class="py-src-variable">None</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">tearDown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">is</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">None</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">client</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">port</span>.<span class="py-src-variable">stopListening</span>()
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_test</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">op</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>, <span class="py-src-parameter">expected</span>):
+ <span class="py-src-variable">creator</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ClientCreator</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">RemoteCalculationClient</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">cb</span>(<span class="py-src-parameter">client</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">client</span> = <span class="py-src-variable">client</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">getattr</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">client</span>, <span class="py-src-variable">op</span>)(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>
+ ).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>, <span class="py-src-variable">expected</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">creator</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">'127.0.0.1'</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">port</span>.<span class="py-src-variable">getHost</span>().<span class="py-src-variable">port</span>
+ ).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cb</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_add</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">&quot;add&quot;</span>, <span class="py-src-number">5</span>, <span class="py-src-number">9</span>, <span class="py-src-number">14</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_subtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">&quot;subtract&quot;</span>, <span class="py-src-number">47</span>, <span class="py-src-number">13</span>, <span class="py-src-number">34</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_multiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">&quot;multiply&quot;</span>, <span class="py-src-number">7</span>, <span class="py-src-number">3</span>, <span class="py-src-number">21</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_divide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">&quot;divide&quot;</span>, <span class="py-src-number">84</span>, <span class="py-src-number">10</span>, <span class="py-src-number">8</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/test/test_remote_2.py"><span class="filename">listings/trial/calculus/test/test_remote_2.py</span></a></div></div>
+
+<p>Recent versions of trial will fail loudly if you remove the
+ <code class="python">stopListening</code> call, which is good.</p>
+
+<p>Also, you should be aware that <code class="python">tearDown</code> will
+called in any case, after success or failure. So don't expect that every
+objects you created in the test method are present, because your tests may
+have failed in the middle.</p>
+
+<p>Trial also has a <code class="python">addCleanup</code> method, which makes
+these kind of cleanups easy and removes the need for <code class="python">tearDown
+</code>. For example, you could remove the code in <code class="python">_test</code>
+this way:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">setUp</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">RemoteCalculationFactory</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">port</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">factory</span>, <span class="py-src-variable">interface</span>=<span class="py-src-string">&quot;127.0.0.1&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">addCleanup</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">port</span>.<span class="py-src-variable">stopListening</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">_test</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">op</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>, <span class="py-src-parameter">expected</span>):
+ <span class="py-src-variable">creator</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ClientCreator</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">RemoteCalculationClient</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">cb</span>(<span class="py-src-parameter">client</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">addCleanup</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">client</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">getattr</span>(<span class="py-src-variable">client</span>, <span class="py-src-variable">op</span>)(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>, <span class="py-src-variable">expected</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">creator</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">'127.0.0.1'</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">port</span>.<span class="py-src-variable">getHost</span>().<span class="py-src-variable">port</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cb</span>)
+</pre>
+
+<p>This remove the need of a tearDown method, and you don't have to check for
+the value of self.client: you only call addCleanup when the client is
+created.</p>
+
+<h3>Handling logged errors<a name="auto11"/></h3>
+
+<p>Currently, if you send an invalid command or invalid arguments to our
+server, it logs an exception and closes the connection. This is a perfectly
+valid behavior, but for the sake of this tutorial, we want to return an error
+to the user if he sends invalid operators, and log any errors on server side.
+So we'll want a test like this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">test_invalidParameters</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">dataReceived</span>(<span class="py-src-string">'add foo bar\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">value</span>(), <span class="py-src-string">&quot;error\r\n&quot;</span>)
+</pre>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+</p><span class="py-src-comment"># -*- test-case-name: calculus.test.test_remote_1 -*-</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">base_3</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Calculation</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CalculationProxy</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span> = <span class="py-src-variable">Calculation</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">m</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">'add'</span>, <span class="py-src-string">'subtract'</span>, <span class="py-src-string">'multiply'</span>, <span class="py-src-string">'divide'</span>]:
+ <span class="py-src-variable">setattr</span>(<span class="py-src-variable">self</span>, <span class="py-src-string">'remote_%s'</span> % <span class="py-src-variable">m</span>, <span class="py-src-variable">getattr</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">calc</span>, <span class="py-src-variable">m</span>))
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteCalculationProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proxy</span> = <span class="py-src-variable">CalculationProxy</span>()
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">op</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>()
+ <span class="py-src-variable">op</span> = <span class="py-src-variable">getattr</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">proxy</span>, <span class="py-src-string">'remote_%s'</span> % (<span class="py-src-variable">op</span>,))
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">op</span>(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">TypeError</span>:
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-string">&quot;error&quot;</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">str</span>(<span class="py-src-variable">result</span>))
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteCalculationFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">Factory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">RemoteCalculationProtocol</span>
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">startLogging</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">stdout</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">RemoteCalculationFactory</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/remote_2.py"><span class="filename">listings/trial/calculus/remote_2.py</span></a></div></div>
+
+<p>If you try something like that, it will not work. Here is the output you should have:</p>
+
+<pre class="shell" xml:space="preserve">
+trial calculus.test.test_remote_3.RemoteCalculationTestCase.test_invalidParameters
+calculus.test.test_remote_3
+ RemoteCalculationTestCase
+ test_invalidParameters ... [ERROR]
+
+===============================================================================
+[ERROR]: calculus.test.test_remote_3.RemoteCalculationTestCase.test_invalidParameters
+
+Traceback (most recent call last):
+ File &quot;/tmp/calculus/remote_2.py&quot;, line 27, in lineReceived
+ result = op(a, b)
+ File &quot;/tmp/calculus/base_3.py&quot;, line 11, in add
+ a, b = self._make_ints(a, b)
+ File &quot;/tmp/calculus/base_3.py&quot;, line 8, in _make_ints
+ raise TypeError
+exceptions.TypeError:
+-------------------------------------------------------------------------------
+Ran 1 tests in 0.004s
+
+FAILED (errors=1)
+</pre>
+
+<p>At first, you could think there is a problem, because you catch this
+exception. But in fact trial doesn't let you do that without controlling it:
+you must expect logged errors and clean them. To do that, you have to use the
+ <code class="python">flushLoggedErrors</code> method. You call it with the
+exception you expect, and it returns the list of exceptions logged since the
+start of the test. Generally, you'll want to check that this list has the
+expected length, or possibly that each exception has an expected message. We do
+the former in our test:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">remote_2</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">RemoteCalculationFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unittest</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">test</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">proto_helpers</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteCalculationTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUp</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">RemoteCalculationFactory</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">buildProtocol</span>((<span class="py-src-string">'127.0.0.1'</span>, <span class="py-src-number">0</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span> = <span class="py-src-variable">proto_helpers</span>.<span class="py-src-variable">StringTransport</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">makeConnection</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_test</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">operation</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>, <span class="py-src-parameter">expected</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">dataReceived</span>(<span class="py-src-string">'%s %d %d\r\n'</span> % (<span class="py-src-variable">operation</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">value</span>()), <span class="py-src-variable">expected</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_add</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'add'</span>, <span class="py-src-number">7</span>, <span class="py-src-number">6</span>, <span class="py-src-number">13</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_subtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'subtract'</span>, <span class="py-src-number">82</span>, <span class="py-src-number">78</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_multiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'multiply'</span>, <span class="py-src-number">2</span>, <span class="py-src-number">8</span>, <span class="py-src-number">16</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_divide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'divide'</span>, <span class="py-src-number">14</span>, <span class="py-src-number">3</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_invalidParameters</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">dataReceived</span>(<span class="py-src-string">'add foo bar\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">value</span>(), <span class="py-src-string">&quot;error\r\n&quot;</span>)
+ <span class="py-src-variable">errors</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">flushLoggedErrors</span>(<span class="py-src-variable">TypeError</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">len</span>(<span class="py-src-variable">errors</span>), <span class="py-src-number">1</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/test/test_remote_3.py"><span class="filename">listings/trial/calculus/test/test_remote_3.py</span></a></div></div>
+
+<h2>Resolve a bug<a name="auto12"/></h2>
+
+<p>A bug was left over during the development of the timeout (probably several
+bugs, but that's not the point), concerning the reuse of the protocol when you
+got a timeout: the connection is not dropped, so you can get timeout forever.
+Generally an user will come to you saying &quot;I have this strange problem on
+my crappy network environment. It seems you could solve it with doing XXX at
+YYY.&quot;</p>
+
+<p>Actually, this bug can be corrected several ways. But if you correct it
+without adding tests, one day you'll face a big problem: regression.
+So the first step is adding a failing test.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calculus</span>.<span class="py-src-variable">client_3</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">RemoteCalculationClient</span>, <span class="py-src-variable">ClientTimeoutError</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">task</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">unittest</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">test</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">proto_helpers</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientCalculationTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUp</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span> = <span class="py-src-variable">proto_helpers</span>.<span class="py-src-variable">StringTransportWithDisconnection</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clock</span> = <span class="py-src-variable">task</span>.<span class="py-src-variable">Clock</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span> = <span class="py-src-variable">RemoteCalculationClient</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">callLater</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">clock</span>.<span class="py-src-variable">callLater</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">makeConnection</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_test</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">operation</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>, <span class="py-src-parameter">expected</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">getattr</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>, <span class="py-src-variable">operation</span>)(<span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">value</span>(), <span class="py-src-string">'%s %d %d\r\n'</span> % (<span class="py-src-variable">operation</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>, <span class="py-src-variable">expected</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">dataReceived</span>(<span class="py-src-string">&quot;%d\r\n&quot;</span> % (<span class="py-src-variable">expected</span>,))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_add</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'add'</span>, <span class="py-src-number">7</span>, <span class="py-src-number">6</span>, <span class="py-src-number">13</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_subtract</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'subtract'</span>, <span class="py-src-number">82</span>, <span class="py-src-number">78</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_multiply</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'multiply'</span>, <span class="py-src-number">2</span>, <span class="py-src-number">8</span>, <span class="py-src-number">16</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_divide</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_test</span>(<span class="py-src-string">'divide'</span>, <span class="py-src-number">14</span>, <span class="py-src-number">3</span>, <span class="py-src-number">4</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_timeout</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">add</span>(<span class="py-src-number">9</span>, <span class="py-src-number">4</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">value</span>(), <span class="py-src-string">'add 9 4\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clock</span>.<span class="py-src-variable">advance</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">timeOut</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">assertFailure</span>(<span class="py-src-variable">d</span>, <span class="py-src-variable">ClientTimeoutError</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_timeoutConnectionLost</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">called</span> = []
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lost</span>(<span class="py-src-parameter">arg</span>):
+ <span class="py-src-variable">called</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">True</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">connectionLost</span> = <span class="py-src-variable">lost</span>
+
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">add</span>(<span class="py-src-number">9</span>, <span class="py-src-number">4</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">tr</span>.<span class="py-src-variable">value</span>(), <span class="py-src-string">'add 9 4\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clock</span>.<span class="py-src-variable">advance</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">proto</span>.<span class="py-src-variable">timeOut</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">check</span>(<span class="py-src-parameter">ignore</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">called</span>, [<span class="py-src-variable">True</span>])
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">assertFailure</span>(<span class="py-src-variable">d</span>, <span class="py-src-variable">ClientTimeoutError</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">check</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/test/test_client_3.py"><span class="filename">listings/trial/calculus/test/test_client_3.py</span></a></div></div>
+<p>What have we done here ?
+<ul>
+ <li>We switched to StringTransportWithDisconnection. This transport manages
+ <code class="python">loseConnection</code> and forwards it to its protocol.</li>
+ <li>We assign the protocol to the transport via the <code class="python">protocol
+ </code> attribute.</li>
+ <li>We check that after a timeout our connection has closed.</li>
+</ul>
+</p>
+
+<p>For doing that, we then use the <code class="python">TimeoutMixin</code>
+class, that does almost everything we want. The great thing is that it almost
+changes nothing to our class.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+</p><span class="py-src-comment"># -*- test-case-name: calculus.test.test_client -*-</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>, <span class="py-src-variable">policies</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientTimeoutError</span>(<span class="py-src-parameter">Exception</span>):
+ <span class="py-src-keyword">pass</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteCalculationClient</span>(<span class="py-src-parameter">object</span>, <span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>, <span class="py-src-parameter">policies</span>.<span class="py-src-parameter">TimeoutMixin</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">results</span> = []
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_timeOut</span> = <span class="py-src-number">60</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">setTimeout</span>(<span class="py-src-variable">None</span>)
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">results</span>.<span class="py-src-variable">pop</span>(<span class="py-src-number">0</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">line</span>))
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">timeoutConnection</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">d</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">results</span>:
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">ClientTimeoutError</span>())
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_sendOperation</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">op</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">results</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">d</span>)
+ <span class="py-src-variable">line</span> = <span class="py-src-string">&quot;%s %d %d&quot;</span> % (<span class="py-src-variable">op</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">line</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">setTimeout</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_timeOut</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;add&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">subtract</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;subtract&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">multiply</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;multiply&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">divide</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_sendOperation</span>(<span class="py-src-string">&quot;divide&quot;</span>, <span class="py-src-variable">a</span>, <span class="py-src-variable">b</span>)
+</pre><div class="caption">Source listing - <a href="listings/trial/calculus/client_3.py"><span class="filename">listings/trial/calculus/client_3.py</span></a></div></div>
+
+<h2>Code coverage<a name="auto13"/></h2>
+
+<p>Code coverage is one of the aspects of software testing that shows how much
+your tests cross (cover) the code of your program. There are different kind of
+measures: path coverage, condition coverage, statement coverage... We'll only
+consider statement coverage here, whether a line has been executed or not.
+</p>
+
+<p>Trial has an option to generate the statement coverage of your tests.
+This option is --coverage. It creates a coverage directory in _trial_temp,
+with a file .cover for every modules used during the tests. The ones
+interesting for us are calculus.base.cover and calculus.remote.cover. In
+front of each line is the number of times you went through during the
+tests, or the marker '&gt;&gt;&gt;&gt;&gt;&gt;' if the line was not
+covered. If you went through all the tutorial to this point, you should
+have complete coverage :).</p>
+
+<p>Again, this is only another useful pointer, but it doesn't mean your
+code is perfect: your tests should consider every possibile input and
+output, to get <strong>full</strong> coverage (condition, path, etc.) as well
+.</p>
+
+<h2>Conclusion<a name="auto14"/></h2>
+
+<p>So what did you learn in this document?
+<ul>
+ <li>How to use the trial command-line tool to run your tests</li>
+ <li>How to use string transports to test individual clients and servers
+ without creating sockets</li>
+ <li>If you really want to create sockets, how to cleanly do it so that it
+ doesn't have bad side effects</li>
+ <li>And some small tips you can't live without.</li>
+</ul>
+If one of the topics still looks cloudy to you, please give us your feedback!
+You can file tickets to improve this document
+<a href="http://twistedmatrix.com/" shape="rect">on the Twisted web site</a>.
+</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/backends.html b/doc/core/howto/tutorial/backends.html
new file mode 100644
index 0000000..f29533e
--- /dev/null
+++ b/doc/core/howto/tutorial/backends.html
@@ -0,0 +1,1348 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: pluggable backends</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: pluggable backends</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Another Back-end</a></li><li><a href="#auto2">Yet Another Back-end: Doing the Standard Thing</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the fifth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part we will add new several new backends to our finger service using
+the component-based architecture developed in <a href="components.html" shape="rect">The
+Evolution of Finger: moving to a component based architecture</a>. This will
+show just how convenient it is to implement new back-ends when we move to a
+component based architecture. Note that here we also use an interface we
+previously wrote, <code>FingerSetterFactory</code>, by supporting one single
+method. We manage to preserve the service's ignorance of the network.</p>
+
+<h2>Another Back-end<a name="auto1"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">pwd</span>
+
+<span class="py-src-comment"># Another back-end</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LocalFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-comment"># need a local finger daemon running for this to work</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessOutput</span>(<span class="py-src-string">&quot;finger&quot;</span>, [<span class="py-src-variable">user</span>])
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>([])
+
+
+<span class="py-src-variable">f</span> = <span class="py-src-variable">LocalFingerService</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19b_changes.py"><span class="filename">listings/finger/finger19b_changes.py</span></a></div></div>
+<p>
+Full source code here: <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">pwd</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a list of strings.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Set the user's status to something.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Set the user's status to something.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">formatUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">l</span> = [<span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;ul&gt;'</span>+<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">l</span>)+<span class="py-src-string">'&lt;/ul&gt;'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">formatUsers</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>==<span class="py-src-string">&quot;&quot;</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>:
+ <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;'</span>%<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">'&lt;p&gt;%s&lt;/p&gt;'</span>%<span class="py-src-variable">m</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-comment"># Another back-end</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LocalFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-comment"># need a local finger daemon running for this to work</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessOutput</span>(<span class="py-src-string">&quot;finger&quot;</span>, [<span class="py-src-variable">user</span>])
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>([])
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">LocalFingerService</span>()
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19b.tac"><span class="filename">listings/finger/finger19b.tac</span></a></div></div>
+</p>
+
+<p>We've already written this, but now we get more for less work:
+the network code is completely separate from the back-end.</p>
+
+
+<h2>Yet Another Back-end: Doing the Standard Thing<a name="auto2"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">pwd</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+
+
+<span class="py-src-comment"># Yet another back-end</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LocalFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">entry</span> = <span class="py-src-variable">pwd</span>.<span class="py-src-variable">getpwnam</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">KeyError</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">&quot;No such user&quot;</span>)
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">file</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">entry</span>[<span class="py-src-number">5</span>],<span class="py-src-string">'.plan'</span>))
+ <span class="py-src-keyword">except</span> (<span class="py-src-variable">IOError</span>, <span class="py-src-variable">OSError</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">&quot;No such user&quot;</span>)
+ <span class="py-src-variable">data</span> = <span class="py-src-variable">f</span>.<span class="py-src-variable">read</span>()
+ <span class="py-src-variable">data</span> = <span class="py-src-variable">data</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">close</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">data</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>([])
+
+
+
+<span class="py-src-variable">f</span> = <span class="py-src-variable">LocalFingerService</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19c_changes.py"><span class="filename">listings/finger/finger19c_changes.py</span></a></div></div>
+<p>
+Full source code here: <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">pwd</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a list of strings.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Set the user's status to something.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Set the user's status to something.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>():
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">formatUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">l</span> = [<span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;ul&gt;'</span>+<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">l</span>)+<span class="py-src-string">'&lt;/ul&gt;'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">formatUsers</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>==<span class="py-src-string">&quot;&quot;</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>:
+ <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;'</span>%<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">'&lt;p&gt;%s&lt;/p&gt;'</span>%<span class="py-src-variable">m</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-comment"># Yet another back-end</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LocalFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">entry</span> = <span class="py-src-variable">pwd</span>.<span class="py-src-variable">getpwnam</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">KeyError</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">&quot;No such user&quot;</span>)
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">file</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">entry</span>[<span class="py-src-number">5</span>],<span class="py-src-string">'.plan'</span>))
+ <span class="py-src-keyword">except</span> (<span class="py-src-variable">IOError</span>, <span class="py-src-variable">OSError</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">&quot;No such user&quot;</span>)
+ <span class="py-src-variable">data</span> = <span class="py-src-variable">f</span>.<span class="py-src-variable">read</span>()
+ <span class="py-src-variable">data</span> = <span class="py-src-variable">data</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">close</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">data</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>([])
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">LocalFingerService</span>()
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19c.tac"><span class="filename">listings/finger/finger19c.tac</span></a></div></div>
+</p>
+
+<p>Not much to say except that now we can be churn out backends like crazy. Feel
+like doing a back-end for <a href="http://www.advogato.org/" shape="rect">Advogato</a>, for
+example? Dig out the XML-RPC client support Twisted has, and get to work!</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/client.html b/doc/core/howto/tutorial/client.html
new file mode 100644
index 0000000..8d1c4b8
--- /dev/null
+++ b/doc/core/howto/tutorial/client.html
@@ -0,0 +1,260 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: a Twisted finger client</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: a Twisted finger client</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Finger Proxy</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the ninth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we develop a client for the finger server: a proxy finger
+server which forwards requests to another finger server.</p>
+
+<h2>Finger Proxy<a name="auto1"/></h2>
+
+<p>Writing new clients with Twisted is much like writing new servers.
+We implement the protocol, which just gathers up all the data, and
+give it to the factory. The factory keeps a deferred which is triggered
+if the connection either fails or succeeds. When we use the client,
+we first make sure the deferred will never fail, by producing a message
+in that case. Implementing a wrapper around client which just returns
+the deferred is a common pattern. While less flexible than
+using the factory directly, it's also more convenient.</p>
+
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+</p><span class="py-src-comment"># finger proxy</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>, <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerClient</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">&quot;\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">buf</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">buf</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">data</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">gotData</span>(<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">buf</span>))
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerClientFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerClient</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">_</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">reason</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">gotData</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">data</span>)
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">finger</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">host</span>, <span class="py-src-parameter">port</span>=<span class="py-src-number">79</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">FingerClientFactory</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>, <span class="py-src-variable">f</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>.<span class="py-src-variable">d</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ProxyFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">host</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'@'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">host</span> = <span class="py-src-string">'127.0.0.1'</span>
+ <span class="py-src-variable">ret</span> = <span class="py-src-variable">finger</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">host</span>)
+ <span class="py-src-variable">ret</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-string">&quot;Could not connect to remote host&quot;</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">ret</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>([])
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">ProxyFingerService</span>()
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">7779</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)).<span class="py-src-variable">setServiceParent</span>(
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/finger/fingerproxy.tac"><span class="filename">listings/finger/fingerproxy.tac</span></a></div></div>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/components.html b/doc/core/howto/tutorial/components.html
new file mode 100644
index 0000000..a2d3586
--- /dev/null
+++ b/doc/core/howto/tutorial/components.html
@@ -0,0 +1,1132 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: moving to a component based architecture</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: moving to a component based architecture</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Write Maintainable Code</a></li><li><a href="#auto2">Advantages of Latest Version</a></li><li><a href="#auto3">Aspect-Oriented Programming</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the fourth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this section of the tutorial, we'll move our code to a component
+architecture so that adding new features is trivial.
+See <a href="../components.html" shape="rect">Interfaces and Adapters</a> for a more
+complete discussion of components.</p>
+
+<h2>Write Maintainable Code<a name="auto1"/></h2>
+
+
+<p>In the last version, the service class was three times longer than any other
+class, and was hard to understand. This was because it turned out to have
+multiple responsibilities. It had to know how to access user information, by
+rereading the file every half minute, but also how to display itself in a myriad
+of protocols. Here, we used the component-based architecture that Twisted
+provides to achieve a separation of concerns. All the service is responsible
+for, now, is supporting <code>getUser</code>/<code>getUsers</code>. It declares
+its support via a call to <code>zope.interface.implements</code>. Then, adapters
+are used to make this service look like an appropriate class for various things:
+for supplying a finger factory to <code>TCPServer</code>, for supplying a
+resource to site's constructor, and to provide an IRC client factory
+for <code>TCPClient</code>. All the adapters use are the methods
+in <code>FingerService</code> they are declared to use:
+<code>getUser</code>/<code>getUsers</code>. We could, of course, skip the
+interfaces and let the configuration code use things
+like <code>FingerFactoryFromService(f)</code> directly. However, using
+interfaces provides the same flexibility inheritance gives: future subclasses
+can override the adapters.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a list of strings.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Set the user's status to something.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">formatUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">l</span> = [<span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;ul&gt;'</span>+<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">l</span>)+<span class="py-src-string">'&lt;/ul&gt;'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">formatUsers</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>==<span class="py-src-string">&quot;&quot;</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>:
+ <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;'</span>%<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">'&lt;p&gt;%s&lt;/p&gt;'</span>%<span class="py-src-variable">m</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">f</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19.tac"><span class="filename">listings/finger/finger19.tac</span></a></div></div>
+
+<h2>Advantages of Latest Version<a name="auto2"/></h2>
+
+<ul>
+<li>Readable -- each class is short</li>
+<li>Maintainable -- each class knows only about interfaces</li>
+<li>Dependencies between code parts are minimized</li>
+<li>Example: writing a new <code>IFingerService</code> is easy</li>
+</ul>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-comment"># Advantages of latest version</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MemoryFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>([<span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">IFingerSetterService</span>])
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+
+
+<span class="py-src-variable">f</span> = <span class="py-src-variable">MemoryFingerService</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">IFingerSetterFactory</span>(<span class="py-src-variable">f</span>), <span class="py-src-variable">interface</span>=<span class="py-src-string">'127.0.0.1'</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19a_changes.py"><span class="filename">listings/finger/finger19a_changes.py</span></a></div></div>
+<p>
+Full source code here: <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">formatUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">l</span> = [<span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;ul&gt;'</span>+<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">l</span>)+<span class="py-src-string">'&lt;/ul&gt;'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">formatUsers</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>==<span class="py-src-string">&quot;&quot;</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>:
+ <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;'</span>%<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">'&lt;p&gt;%s&lt;/p&gt;'</span>%<span class="py-src-variable">m</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MemoryFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>([<span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">IFingerSetterService</span>])
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">MemoryFingerService</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">IFingerSetterFactory</span>(<span class="py-src-variable">f</span>), <span class="py-src-variable">interface</span>=<span class="py-src-string">'127.0.0.1'</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19a.tac"><span class="filename">listings/finger/finger19a.tac</span></a></div></div>
+</p>
+
+<h2>Aspect-Oriented Programming<a name="auto3"/></h2>
+
+<p>At last, an example of aspect-oriented programming that isn't about logging
+or timing. This code is actually useful! Watch how aspect-oriented programming
+helps you write less code and have fewer dependencies!
+</p>
+
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/configuration.html b/doc/core/howto/tutorial/configuration.html
new file mode 100644
index 0000000..e905587
--- /dev/null
+++ b/doc/core/howto/tutorial/configuration.html
@@ -0,0 +1,870 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: configuration and packaging of the finger service</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: configuration and packaging of the finger service</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Plugins</a></li><li><a href="#auto2">OS Integration</a></li><ul><li><a href="#auto3">Debian</a></li><li><a href="#auto4">Red Hat / Mandrake</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the eleventh part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we make it easier for non-programmers to configure a finger
+server and show how to package it in the .deb and RPM package formats. Plugins
+are discussed further in the <a href="../plugin.html" shape="rect">Twisted Plugin System</a>
+howto. Writing twistd plugins is covered in <a href="../tap.html" shape="rect">Writing a
+twistd Plugin</a>, and .tac applications are covered in <a href="../application.html" shape="rect">Using the Twisted Application Framework</a>.</p>
+
+<h2>Plugins<a name="auto1"/></h2>
+
+<p>So far, the user had to be somewhat of a programmer to be able to configure
+stuff. Maybe we can eliminate even that? Move old code
+to <code>finger/__init__.py</code> and...</p>
+<p>
+Full source code for finger module here: <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+</p><span class="py-src-comment"># finger.py module</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>, <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a list of strings.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Set the user's status to something.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">template</span> = <span class="py-src-string">&quot;&quot;&quot;&lt;html&gt;&lt;head&gt;&lt;title&gt;Users&lt;/title&gt;&lt;/head&gt;&lt;body&gt;
+ &lt;h1&gt;Users&lt;/h1&gt;
+ &lt;ul&gt;
+ %(users)s
+ &lt;/ul&gt;
+ &lt;/body&gt;
+ &lt;/html&gt;&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span> == <span class="py-src-string">''</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>
+ <span class="py-src-keyword">elif</span> <span class="py-src-variable">path</span> == <span class="py-src-string">'RPC2'</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">users</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">cbUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">template</span> % {<span class="py-src-string">'users'</span>: <span class="py-src-string">''</span>.<span class="py-src-variable">join</span>([
+ <span class="py-src-comment"># Name should be quoted properly these uses.</span>
+ <span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">name</span>, <span class="py-src-variable">name</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">name</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>])})
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+ <span class="py-src-variable">users</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbUsers</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">ebUsers</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>(<span class="py-src-variable">err</span>, <span class="py-src-string">&quot;UserStatusTree failed&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+ <span class="py-src-variable">users</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">ebUsers</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">template</span>=<span class="py-src-string">'''&lt;html&gt;&lt;head&gt;&lt;title&gt;%(title)s&lt;/title&gt;&lt;/head&gt;
+ &lt;body&gt;&lt;h1&gt;%(name)s&lt;/h1&gt;&lt;p&gt;%(status)s&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;'''</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">cbStatus</span>(<span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">template</span> % {
+ <span class="py-src-string">'title'</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>,
+ <span class="py-src-string">'name'</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>,
+ <span class="py-src-string">'status'</span>: <span class="py-src-variable">status</span>})
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+ <span class="py-src-variable">status</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbStatus</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">ebStatus</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>(<span class="py-src-variable">err</span>, <span class="py-src-string">&quot;UserStatus failed&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+ <span class="py-src-variable">status</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">ebStatus</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IPerspectiveFinger</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">username</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a user's status.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a user's status.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PerspectiveFingerFromService</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPerspectiveFinger</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">username</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">PerspectiveFingerFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IPerspectiveFinger</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ServerContextFactory</span>:
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getContext</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">SSL</span>.<span class="py-src-variable">Context</span>(<span class="py-src-variable">SSL</span>.<span class="py-src-variable">SSLv23_METHOD</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_certificate_file</span>(<span class="py-src-string">'server.pem'</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_privatekey_file</span>(<span class="py-src-string">'server.pem'</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">ctx</span>
+
+
+
+<span class="py-src-comment"># Easy configuration</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span>(<span class="py-src-parameter">config</span>):
+ <span class="py-src-comment"># finger on port 79</span>
+ <span class="py-src-variable">s</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">MultiService</span>()
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-variable">config</span>[<span class="py-src-string">'file'</span>])
+ <span class="py-src-variable">h</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>))
+ <span class="py-src-variable">h</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+
+ <span class="py-src-comment"># website on port 8000</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>)
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">templateDirectory</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'templates'</span>]
+ <span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>)
+ <span class="py-src-variable">j</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">site</span>)
+ <span class="py-src-variable">j</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># ssl on port 443</span>
+<span class="py-src-comment"># if config.get('ssl'):</span>
+<span class="py-src-comment"># k = internet.SSLServer(443, site, ServerContextFactory())</span>
+<span class="py-src-comment"># k.setServiceParent(s)</span>
+
+ <span class="py-src-comment"># irc fingerbot</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-string">'ircnick'</span>):
+ <span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+ <span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'ircnick'</span>]
+ <span class="py-src-variable">ircserver</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'ircserver'</span>]
+ <span class="py-src-variable">b</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">ircserver</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>)
+ <span class="py-src-variable">b</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># Pespective Broker on port 8889</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-string">'pbport'</span>):
+ <span class="py-src-variable">m</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(
+ <span class="py-src-variable">int</span>(<span class="py-src-variable">config</span>[<span class="py-src-string">'pbport'</span>]),
+ <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">IPerspectiveFinger</span>(<span class="py-src-variable">f</span>)))
+ <span class="py-src-variable">m</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">s</span>
+</pre><div class="caption">finger module - <a href="listings/finger/finger/finger.py"><span class="filename">listings/finger/finger/finger.py</span></a></div></div>
+</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-comment"># finger/tap.py</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">interfaces</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">finger</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optParameters</span> = [
+ [<span class="py-src-string">'file'</span>, <span class="py-src-string">'f'</span>, <span class="py-src-string">'/etc/users'</span>],
+ [<span class="py-src-string">'templates'</span>, <span class="py-src-string">'t'</span>, <span class="py-src-string">'/usr/share/finger/templates'</span>],
+ [<span class="py-src-string">'ircnick'</span>, <span class="py-src-string">'n'</span>, <span class="py-src-string">'fingerbot'</span>],
+ [<span class="py-src-string">'ircserver'</span>, <span class="py-src-variable">None</span>, <span class="py-src-string">'irc.freenode.net'</span>],
+ [<span class="py-src-string">'pbport'</span>, <span class="py-src-string">'p'</span>, <span class="py-src-number">8889</span>],
+ ]
+
+ <span class="py-src-variable">optFlags</span> = [[<span class="py-src-string">'ssl'</span>, <span class="py-src-string">'s'</span>]]
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span>(<span class="py-src-parameter">config</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">finger</span>.<span class="py-src-variable">makeService</span>(<span class="py-src-variable">config</span>)
+</pre><div class="caption">finger/tap.py - <a href="listings/finger/finger/tap.py"><span class="filename">listings/finger/finger/tap.py</span></a></div></div>
+
+<p>And register it all:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">service</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ServiceMaker</span>
+
+<span class="py-src-variable">finger</span> = <span class="py-src-variable">ServiceMaker</span>(
+ <span class="py-src-string">'finger'</span>, <span class="py-src-string">'finger.tap'</span>, <span class="py-src-string">'Run a finger service'</span>, <span class="py-src-string">'finger'</span>)
+</pre><div class="caption">
+twisted/plugins/finger_tutorial.py
+ - <a href="listings/finger/twisted/plugins/finger_tutorial.py"><span class="filename">listings/finger/twisted/plugins/finger_tutorial.py</span></a></div></div>
+
+<p>Note that the second argument to <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.ServiceMaker.html" title="twisted.application.service.ServiceMaker">ServiceMaker</a></code>,
+<code>finger.tap</code>, is a reference to a module
+(<code>finger/tap.py</code>), not to a filename.</p>
+
+<p>And now, the following works</p>
+
+<pre class="shell" xml:space="preserve">
+% sudo twistd -n finger --file=/etc/users --ircnick=fingerbot
+</pre>
+
+<p>
+ For more details about this, see the <a href="../tap.html" shape="rect">twistd plugin
+ documentation</a>.
+</p>
+
+<h2>OS Integration<a name="auto2"/></h2>
+
+<p>If we already have the <q>finger</q> package installed in
+ <code>PYTHONPATH</code> (e.g. we added it to <code>site-packages</code>), we
+can achieve easy integration:</p>
+
+<h3>Debian<a name="auto3"/></h3>
+
+<pre class="shell" xml:space="preserve">
+% tap2deb --unsigned -m &quot;Foo &lt;foo@example.com&gt;&quot; --type=python finger.tac
+% sudo dpkg -i .build/*.deb
+</pre>
+
+<h3>Red Hat / Mandrake<a name="auto4"/></h3>
+
+<pre class="shell" xml:space="preserve">
+% tap2rpm --type=python finger.tac
+% sudo rpm -i *.rpm
+</pre>
+
+<p>These packages will properly install and register <code>init.d</code>
+scripts, etc. for the given file.</p>
+
+<p>If it doesn't work on your favorite OS: patches accepted!</p>
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/factory.html b/doc/core/howto/tutorial/factory.html
new file mode 100644
index 0000000..cc31631
--- /dev/null
+++ b/doc/core/howto/tutorial/factory.html
@@ -0,0 +1,713 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: using a single factory for
+ multiple protocols</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: using a single factory for
+ multiple protocols</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Support HTTPS</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the eighth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we add HTTPS support to our web frontend, showing how to have a
+single factory listen on multiple ports. More information on using SSL in
+Twisted can be found in the <a href="../ssl.html" shape="rect">SSL howto</a>.</p>
+
+<h2>Support HTTPS<a name="auto1"/></h2>
+
+<p>All we need to do to code an HTTPS site is just write a context factory (in
+this case, which loads the certificate from a certain file) and then use the
+twisted.application.internet.SSLServer method. Note that one factory (in this
+case, a site) can listen on multiple ports with multiple protocols.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">microdom</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a list of strings.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Set the user's status to something.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>=<span class="py-src-variable">service</span>
+
+ <span class="py-src-comment"># add a specific child for the path &quot;RPC2&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;RPC2&quot;</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-comment"># need to do this for resources at the root of the site</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;&quot;</span>, <span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">users</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">userOutput</span> = <span class="py-src-string">''</span>.<span class="py-src-variable">join</span>([<span class="py-src-string">&quot;&lt;li&gt;&lt;a href=\&quot;%s\&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;&quot;</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>])
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;
+ &lt;html&gt;&lt;head&gt;&lt;title&gt;Users&lt;/title&gt;&lt;/head&gt;&lt;body&gt;
+ &lt;h1&gt;Users&lt;/h1&gt;
+ &lt;ul&gt;
+ %s
+ &lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % <span class="py-src-variable">userOutput</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">user</span>=<span class="py-src-variable">path</span>, <span class="py-src-variable">service</span>=<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">status</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;&lt;html&gt;&lt;head&gt;&lt;title&gt;%s&lt;/title&gt;&lt;/head&gt;
+ &lt;body&gt;&lt;h1&gt;%s&lt;/h1&gt;
+ &lt;p&gt;%s&lt;/p&gt;
+ &lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>))
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IPerspectiveFinger</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">username</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a user's status.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a user's status.
+ &quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PerspectiveFingerFromService</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPerspectiveFinger</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">username</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">PerspectiveFingerFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IPerspectiveFinger</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ServerContextFactory</span>:
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getContext</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">SSL</span>.<span class="py-src-variable">Context</span>(<span class="py-src-variable">SSL</span>.<span class="py-src-variable">SSLv23_METHOD</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_certificate_file</span>(<span class="py-src-string">'server.pem'</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_privatekey_file</span>(<span class="py-src-string">'server.pem'</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">ctx</span>
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">f</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">site</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">SSLServer</span>(<span class="py-src-number">443</span>, <span class="py-src-variable">site</span>, <span class="py-src-variable">ServerContextFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8889</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">IPerspectiveFinger</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger22.py"><span class="filename">listings/finger/finger22.py</span></a></div></div>
+
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/index.html b/doc/core/howto/tutorial/index.html
new file mode 100644
index 0000000..e181e17
--- /dev/null
+++ b/doc/core/howto/tutorial/index.html
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted from Scratch, or The Evolution of Finger</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted from Scratch, or The Evolution of Finger</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Contents</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p>
+Twisted is a big system. People are often daunted when they approach it. It's
+hard to know where to start looking.
+</p>
+
+<p>
+This guide builds a full-fledged Twisted application from the ground up, using
+most of the important bits of the framework. There is a lot of code, but don't
+be afraid.
+</p>
+
+<p>
+The application we are looking at is a <q>finger</q> service, along the
+lines of the familiar service traditionally provided by UNIXâ„¢ servers.
+We will extend this service slightly beyond the standard, in order to
+demonstrate some of Twisted's higher-level features.
+</p>
+
+<p>
+Each section of the tutorial dives straight into applications for various
+Twisted topics. These topics have their own introductory howtos listed in
+the <a href="../index.html" shape="rect">core howto index</a> and in the documentation for
+other Twisted projects like Twisted Web and Twisted Words. There are at least
+three ways to use this tutorial: you may find it useful to read through the rest
+of the topics listed in the <a href="../index.html" shape="rect">core howto index</a> before
+working through the finger tutorial, work through the finger tutorial and then
+go back and hit the introductory material that is relevant to the Twisted
+project you're working on, or read the introductory material one piece at a time
+as it comes up in the finger tutorial.
+</p>
+
+<h2>Contents<a name="auto1"/></h2>
+
+<p>
+This tutorial is split into eleven parts:
+</p>
+
+<ol>
+<li><a href="intro.html" shape="rect">The Evolution of Finger: building a simple
+finger service</a></li>
+<li><a href="protocol.html" shape="rect">The Evolution of Finger: adding features
+to the finger service</a></li>
+<li><a href="style.html" shape="rect">The Evolution of Finger: cleaning up the
+finger code</a></li>
+<li><a href="components.html" shape="rect">The Evolution of Finger: moving to a
+component based architecture</a></li>
+<li><a href="backends.html" shape="rect">The Evolution of Finger: pluggable
+backends</a></li>
+<li><a href="web.html" shape="rect">The Evolution of Finger: a web
+frontend</a></li>
+<li><a href="pb.html" shape="rect">The Evolution of Finger: Twisted client
+support using Perspective Broker</a></li>
+<li><a href="factory.html" shape="rect">The Evolution of Finger: using a single
+factory for multiple protocols</a></li>
+<li><a href="client.html" shape="rect">The Evolution of Finger: a Twisted finger
+client</a></li>
+<li><a href="library.html" shape="rect">The Evolution of Finger: making a finger library</a></li>
+<li><a href="configuration.html" shape="rect">The Evolution of Finger:
+configuration and packaging of the finger service</a></li>
+</ol>
+
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/intro.html b/doc/core/howto/tutorial/intro.html
new file mode 100644
index 0000000..cad8826
--- /dev/null
+++ b/doc/core/howto/tutorial/intro.html
@@ -0,0 +1,725 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: building a simple finger service</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: building a simple finger service</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Refuse Connections</a></li><ul><li><a href="#auto2">The Reactor</a></li></ul><li><a href="#auto3">Do Nothing</a></li><li><a href="#auto4">Drop Connections</a></li><li><a href="#auto5">Read Username, Drop Connections</a></li><li><a href="#auto6">Read Username, Output Error, Drop Connections</a></li><li><a href="#auto7">Output From Empty Factory</a></li><li><a href="#auto8">Output from Non-empty Factory</a></li><li><a href="#auto9">Use Deferreds</a></li><li><a href="#auto10">Run 'finger' Locally</a></li><li><a href="#auto11">Read Status from the Web</a></li><li><a href="#auto12">Use Application</a></li><li><a href="#auto13">twistd</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p>This is the first part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>If you're not familiar with 'finger' it's probably because it's not used as
+much nowadays as it used to be. Basically, if you run <code>finger nail</code>
+or <code>finger nail@example.com</code> the target computer spits out some
+information about the user named <code>nail</code>. For instance:</p>
+
+<pre class="shell" xml:space="preserve">
+Login: nail Name: Nail Sharp
+Directory: /home/nail Shell: /usr/bin/sh
+Last login Wed Mar 31 18:32 2004 (PST)
+New mail received Thu Apr 1 10:50 2004 (PST)
+ Unread since Thu Apr 1 10:50 2004 (PST)
+No Plan.
+</pre>
+
+<p>If the target computer does not have
+the <code>fingerd</code> <a href="../glossary.html#Daemon" shape="rect">daemon</a>
+running you'll get a &quot;Connection Refused&quot; error. Paranoid sysadmins
+keep <code>fingerd</code> off or limit the output to hinder crackers
+and harassers. The above format is the standard <code>fingerd</code>
+default, but an alternate implementation can output anything it wants,
+such as automated responsibility status for everyone in an
+organization. You can also define pseudo &quot;users&quot;, which are
+essentially keywords.</p>
+
+<p>This portion of the tutorial makes use of factories and protocols as
+introduced in the <a href="../servers.html" shape="rect">Writing a TCP Server howto</a> and
+deferreds as introduced in <a href="../defer.html" shape="rect">Using Deferreds</a>
+and <a href="../gendefer.html" shape="rect">Generating Deferreds</a>. Services and
+applications are discussed in <a href="../application.html" shape="rect">Using the Twisted
+Application Framework</a>.</p>
+
+<p>By the end of this section of the tutorial, our finger server will answer
+TCP finger requests on port 1079, and will read data from the web.</p>
+
+<h2>Refuse Connections<a name="auto1"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger01.py"><span class="filename">listings/finger/finger01.py</span></a></div></div>
+
+<p>This example only runs the reactor. It will consume almost no CPU
+resources. As it is not listening on any port, it can't respond to network
+requests — nothing at all will happen until we interrupt the program. At
+this point if you run <code>finger nail</code> or <code>telnet localhost
+1079</code>, you'll get a &quot;Connection refused&quot; error since there's no daemon
+running to respond. Not very useful, perhaps — but this is the skeleton
+inside which the Twisted program will grow.
+</p>
+
+<p>As implied above, at various points in this tutorial you'll want to
+observe the behavior of the server being developed. Unless you have a
+finger program which can use an alternate port, the easiest way to do this
+is with a telnet client. <code>telnet localhost 1079</code> will connect to
+the local host on port 1079, where a finger server will eventually be
+listening.</p>
+
+<h3>The Reactor<a name="auto2"/></h3>
+
+<p>You don't call Twisted, Twisted calls you. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code> is Twisted's main event loop, similar to
+the main loop in other toolkits available in Python (Qt, wx, and Gtk). There is
+exactly one reactor in any running Twisted application. Once started it loops
+over and over again, responding to network events and making scheduled calls to
+code.</p>
+
+<p>Note that there are actually several different reactors to choose
+from; <code>from twisted.internet import reactor</code> returns the
+current reactor. If you haven't chosen a reactor class yet, it
+automatically chooses the default. See
+the <a href="../reactor-basics.html" shape="rect">Reactor Basics HOWTO</a> for
+more information.</p>
+
+<h2>Do Nothing<a name="auto3"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger02.py"><span class="filename">listings/finger/finger02.py</span></a></div></div>
+
+<p>Here, <code>reactor.listenTCP</code> opens port 1079. (The number 1079 is a
+reminder that eventually we want to run on port 79, the standard port for
+finger servers.) The specified factory, <code>FingerFactory</code>, is used to
+handle incoming requests on that port. Specifically, for each request, the
+reactor calls the factory's <code>buildProtocol</code> method, which in this
+case causes <code>FingerProtocol</code> to be instantiated. Since the protocol
+defined here does not actually respond to any events, connections to 1079 will
+be accepted, but the input ignored.</p>
+
+<p>A Factory is the proper place for data that you want to make available to
+the protocol instances, since the protocol instances are garbage collected when
+the connection is closed.</p>
+
+
+<h2>Drop Connections<a name="auto4"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger03.py"><span class="filename">listings/finger/finger03.py</span></a></div></div>
+
+<p>Here we add to the protocol the ability to respond to the event of beginning
+a connection — by terminating it. Perhaps not an interesting behavior,
+but it is already close to behaving according to the letter of the standard
+finger protocol. After all, there is no requirement to send any data to the
+remote connection in the standard. The only problem, as far as the standard is
+concerned, is that we terminate the connection too soon. A client which is slow
+enough will see his <code>send()</code> of the username result in an error.</p>
+
+
+<h2>Read Username, Drop Connections<a name="auto5"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger04.py"><span class="filename">listings/finger/finger04.py</span></a></div></div>
+
+<p>Here we make <code>FingerProtocol</code> inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.protocols.basic.LineReceiver.html" title="twisted.protocols.basic.LineReceiver">LineReceiver</a></code>, so that we get data-based
+events on a line-by-line basis. We respond to the event of receiving the line
+with shutting down the connection.</p>
+
+<p>If you use a telnet client to interact with this server, the result will
+look something like this:</p>
+
+<pre class="shell" xml:space="preserve">
+$ telnet localhost 1079
+Trying 127.0.0.1...
+Connected to localhost.localdomain.
+alice
+Connection closed by foreign host.
+</pre>
+
+<p>Congratulations, this is the first standard-compliant version of the code.
+However, usually people actually expect some data about users to be
+transmitted.</p>
+
+<h2>Read Username, Output Error, Drop Connections<a name="auto6"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;No such user\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger05.py"><span class="filename">listings/finger/finger05.py</span></a></div></div>
+
+<p>Finally, a useful version. Granted, the usefulness is somewhat limited by
+the fact that this version only prints out a <q>No such user</q> message. It
+could be used for devastating effect in honey-pots (decoy servers), of
+course.</p>
+
+
+<h2>Output From Empty Factory<a name="auto7"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-comment"># Read username, output from empty factory, drop connections</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)+<span class="py-src-string">&quot;\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;No such user&quot;</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger06.py"><span class="filename">listings/finger/finger06.py</span></a></div></div>
+
+<p>The same behavior, but finally we see what usefulness the
+factory has: as something that does not get constructed for
+every connection, it can be in charge of the user database.
+In particular, we won't have to change the protocol if
+the user database back-end changes.</p>
+
+
+<h2>Output from Non-empty Factory<a name="auto8"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-comment"># Read username, output from non-empty factory, drop connections</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)+<span class="py-src-string">&quot;\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger07.py"><span class="filename">listings/finger/finger07.py</span></a></div></div>
+
+<p>Finally, a really useful finger database. While it does not
+supply information about logged in users, it could be used to
+distribute things like office locations and internal office
+numbers. As hinted above, the factory is in charge of keeping
+the user database: note that the protocol instance has not
+changed. This is starting to look good: we really won't have
+to keep tweaking our protocol.</p>
+
+
+<h2>Use Deferreds<a name="auto9"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-comment"># Read username, output from non-empty factory, drop connections</span>
+<span class="py-src-comment"># Use deferreds, to minimize synchronicity assumptions</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger08.py"><span class="filename">listings/finger/finger08.py</span></a></div></div>
+
+<p>But, here we tweak it just for the hell of it. Yes, while the
+previous version worked, it did assume the result of getUser is
+always immediately available. But what if instead of an in-memory
+database, we would have to fetch the result from a remote Oracle server? By
+allowing getUser to return a Deferred, we make it easier for the data to be
+retrieved asynchronously so that the CPU can be used for other tasks in the
+meanwhile.</p>
+
+<p>As described in the <a href="../defer.html" shape="rect">Deferred HOWTO</a>, Deferreds
+allow a program to be driven by events. For instance, if one task in a program
+is waiting on data, rather than have the CPU (and the program!) idly waiting
+for that data (a process normally called 'blocking'), the program can perform
+other operations in the meantime, and waits for some signal that data is ready
+to be processed before returning to that process.</p>
+
+<p>In brief, the code in <code>FingerFactory</code> above creates a
+Deferred, to which we start to attach <em>callbacks</em>. The
+deferred action in <code>FingerFactory</code> is actually a
+fast-running expression consisting of one dictionary
+method, <code>get</code>. Since this action can execute without
+delay, <code>FingerFactory.getUser</code>
+uses <code>defer.succeed</code> to create a Deferred which already has
+a result, meaning its return value will be passed immediately to the
+first callback function, which turns out to
+be <code>FingerProtocol.writeResponse</code>. We've also defined
+an <em>errback</em> (appropriately
+named <code>FingerProtocol.onError</code>) that will be called instead
+of <code>writeResponse</code> if something goes wrong.</p>
+
+<h2>Run 'finger' Locally<a name="auto10"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-comment"># Read username, output from factory interfacing to OS, drop connections</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessOutput</span>(<span class="py-src-string">&quot;finger&quot;</span>, [<span class="py-src-variable">user</span>])
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger09.py"><span class="filename">listings/finger/finger09.py</span></a></div></div>
+
+<p>This example also makes use of a
+Deferred. <code>twisted.internet.utils.getProcessOutput</code> is a
+non-blocking version of Python's <code>commands.getoutput</code>: it
+runs a shell command (<code>finger</code>, in this case) and captures
+its standard output. However, <code>getProcessOutput</code> returns a
+Deferred instead of the output itself.
+Since <code>FingerProtocol.lineReceived</code> is already expecting a
+Deferred to be returned by <code>getUser</code>, it doesn't need to be
+changed, and it returns the standard output as the finger result.</p>
+
+<p>Note that in this case the shell's built-in <code>finger</code> command is
+simply run with whatever arguments it is given. This is probably insecure, so
+you probably don't want a real server to do this without a lot more validation
+of the user input. This will do exactly what the standard version of the finger
+server does.</p>
+
+<h2>Read Status from the Web<a name="auto11"/></h2>
+
+<p>The web. That invention which has infiltrated homes around the
+world finally gets through to our invention. In this case we use the
+built-in Twisted web client
+via <code>twisted.web.client.getPage</code>, a non-blocking version of
+Python's <code>urllib2.urlopen(URL).read()</code>.
+Like <code>getProcessOutput</code> it returns a Deferred which will be
+called back with a string, and can thus be used as a drop-in
+replacement.</p>
+
+<p>Thus, we have examples of three different database back-ends, none of which
+change the protocol class. In fact, we will not have to change the protocol
+again until the end of this tutorial: we have achieved, here, one truly usable
+class.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-comment"># Read username, output from factory interfacing to web, drop connections</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">client</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">prefix</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">prefix</span>=<span class="py-src-variable">prefix</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">client</span>.<span class="py-src-variable">getPage</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">prefix</span>+<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>(<span class="py-src-variable">prefix</span>=<span class="py-src-string">'http://livejournal.com/~'</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger10.py"><span class="filename">listings/finger/finger10.py</span></a></div></div>
+
+<h2>Use Application<a name="auto12"/></h2>
+
+<p>Up until now, we faked. We kept using port 1079, because really, who wants to
+run a finger server with root privileges? Well, the common solution
+is <q>privilege shedding</q>: after binding to the network, become a different,
+less privileged user. We could have done it ourselves, but Twisted has a
+built-in way to do it. We will create a snippet as above, but now we will define
+an application object. That object will have <code>uid</code>
+and <code>gid</code> attributes. When running it (later we will see how) it will
+bind to ports, shed privileges and then run.</p>
+
+<p>Read on to find out how to run this code using the twistd utility.</p>
+
+<h2>twistd<a name="auto13"/></h2>
+
+<p>This is how to run <q>Twisted Applications</q> — files which define an
+'application'. A daemon is expected to adhere to certain behavioral standards
+so that standard tools can stop/start/query them. If a Twisted application is
+run via twistd, the TWISTed Daemonizer, all this behavioral stuff will be
+handled for you. twistd does everything a daemon can be expected to —
+shuts down stdin/stdout/stderr, disconnects from the terminal and can even
+change runtime directory, or even the root filesystems. In short, it does
+everything so the Twisted application developer can concentrate on writing his
+networking code.</p>
+
+<pre class="shell" xml:space="preserve">
+root% twistd -ny finger11.tac # just like before
+root% twistd -y finger11.tac # daemonize, keep pid in twistd.pid
+root% twistd -y finger11.tac --pidfile=finger.pid
+root% twistd -y finger11.tac --rundir=/
+root% twistd -y finger11.tac --chroot=/var
+root% twistd -y finger11.tac -l /var/log/finger.log
+root% twistd -y finger11.tac --syslog # just log to syslog
+root% twistd -y finger11.tac --syslog --prefix=twistedfinger # use given prefix
+</pre>
+
+<p>There are several ways to tell twistd where your application is; here we
+show how it is done using the <code>application</code> global variable in a
+Python source file (a <a href="../glossary.html#TAC" shape="rect">Twisted Application
+Configuration</a> file).</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+</p><span class="py-src-comment"># Read username, output from non-empty factory, drop connections</span>
+<span class="py-src-comment"># Use deferreds, to minimize synchronicity assumptions</span>
+<span class="py-src-comment"># Write application. Save in 'finger.tpy'</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">FingerFactory</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">factory</span>).<span class="py-src-variable">setServiceParent</span>(
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/finger/finger11.tac"><span class="filename">listings/finger/finger11.tac</span></a></div></div>
+
+
+<p>Instead of using <code>reactor.listenTCP</code> as in the above
+examples, here we are using its application-aware
+counterpart, <code>internet.TCPServer</code>. Notice that when it is
+instantiated, the application object itself does not reference either
+the protocol or the factory. Any services (such as TCPServer) which
+have the application as their parent will be started when the
+application is started by twistd. The application object is more
+useful for returning an object that supports the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IServiceCollection.html" title="twisted.application.service.IServiceCollection">IServiceCollection</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IProcess.html" title="twisted.application.service.IProcess">IProcess</a></code>,
+and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.persisted.sob.IPersistable.html" title="twisted.persisted.sob.IPersistable">sob.IPersistable</a></code>
+interfaces with the given parameters; we'll be seeing these in the
+next part of the tutorial. As the parent of the TCPServer we opened,
+the application lets us manage the TCPServer.</p>
+
+<p>With the daemon running on the standard finger port, you can test it with
+the standard finger command: <code>finger moshez</code>.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/library.html b/doc/core/howto/tutorial/library.html
new file mode 100644
index 0000000..fd426fe
--- /dev/null
+++ b/doc/core/howto/tutorial/library.html
@@ -0,0 +1,271 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: making a finger library</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: making a finger library</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Organization</a></li><li><a href="#auto2">Easy Configuration</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the tenth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we separate the application code that launches a finger service
+from the library code which defines a finger service, placing the application in
+a Twisted Application Configuration (.tac) file. We also move configuration
+(such as HTML templates) into separate files. Configuration and deployment with
+.tac and twistd are introduced in <a href="../application.html" shape="rect">Using the
+Twisted Application Framework</a>.</p>
+
+<h2>Organization<a name="auto1"/></h2>
+
+<p>Now this code, while quite modular and well-designed, isn't
+properly organized. Everything above the <code>application=</code> belongs in a
+module, and the HTML templates all belong in separate files.
+</p>
+
+<p>We can use the <code>templateFile</code> and <code>templateDirectory</code>
+attributes to indicate what HTML template file to use for each Page, and where
+to look for it.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+</p><span class="py-src-comment"># organized-finger.tac</span>
+<span class="py-src-comment"># eg: twistd -ny organized-finger.tac</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">finger</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>, <span class="py-src-variable">strports</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">finger</span>.<span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">finger</span>.<span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">site</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">SSLServer</span>(<span class="py-src-number">443</span>, <span class="py-src-variable">site</span>, <span class="py-src-variable">finger</span>.<span class="py-src-variable">ServerContextFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+
+<span class="py-src-variable">i</span> = <span class="py-src-variable">finger</span>.<span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8889</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">finger</span>.<span class="py-src-variable">IPerspectiveFinger</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/organized-finger.tac"><span class="filename">listings/finger/organized-finger.tac</span></a></div></div>
+
+<p>
+Note that our program is now quite separated. We have:
+<ul>
+ <li>Code (in the module)</li>
+ <li>Configuration (file above)</li>
+ <li>Presentation (templates)</li>
+ <li>Content (<code>/etc/users</code>)</li>
+ <li>Deployment (twistd)</li>
+</ul>
+
+Prototypes don't need this level of separation, so our earlier examples all
+bunched together. However, real applications do. Thankfully, if we write our
+code correctly, it is easy to achieve a good separation of parts.
+</p>
+
+
+<h2>Easy Configuration<a name="auto2"/></h2>
+
+<p>We can also supply easy configuration for common cases with a makeService
+method that will also help build .tap files later:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+</p><span class="py-src-comment"># Easy configuration</span>
+<span class="py-src-comment"># makeService from finger module</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span>(<span class="py-src-parameter">config</span>):
+ <span class="py-src-comment"># finger on port 79</span>
+ <span class="py-src-variable">s</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">MultiService</span>()
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-variable">config</span>[<span class="py-src-string">'file'</span>])
+ <span class="py-src-variable">h</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>))
+ <span class="py-src-variable">h</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># website on port 8000</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>)
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">templateDirectory</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'templates'</span>]
+ <span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>)
+ <span class="py-src-variable">j</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">site</span>)
+ <span class="py-src-variable">j</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># ssl on port 443</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">get</span>(<span class="py-src-string">'ssl'</span>):
+ <span class="py-src-variable">k</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">SSLServer</span>(<span class="py-src-number">443</span>, <span class="py-src-variable">site</span>, <span class="py-src-variable">ServerContextFactory</span>())
+ <span class="py-src-variable">k</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># irc fingerbot</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-string">'ircnick'</span>):
+ <span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+ <span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'ircnick'</span>]
+ <span class="py-src-variable">ircserver</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'ircserver'</span>]
+ <span class="py-src-variable">b</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">ircserver</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>)
+ <span class="py-src-variable">b</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># Pespective Broker on port 8889</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-string">'pbport'</span>):
+ <span class="py-src-variable">m</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(
+ <span class="py-src-variable">int</span>(<span class="py-src-variable">config</span>[<span class="py-src-string">'pbport'</span>]),
+ <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">IPerspectiveFinger</span>(<span class="py-src-variable">f</span>)))
+ <span class="py-src-variable">m</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">s</span>
+</pre><div class="caption">Source listing - <a href="listings/finger/finger_config.py"><span class="filename">listings/finger/finger_config.py</span></a></div></div>
+
+<p>And we can write simpler files now:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+</p><span class="py-src-comment"># simple-finger.tac</span>
+<span class="py-src-comment"># eg: twistd -ny simple-finger.tac</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">finger</span>
+
+<span class="py-src-variable">options</span> = { <span class="py-src-string">'file'</span>: <span class="py-src-string">'/etc/users'</span>,
+ <span class="py-src-string">'templates'</span>: <span class="py-src-string">'/usr/share/finger/templates'</span>,
+ <span class="py-src-string">'ircnick'</span>: <span class="py-src-string">'fingerbot'</span>,
+ <span class="py-src-string">'ircserver'</span>: <span class="py-src-string">'irc.freenode.net'</span>,
+ <span class="py-src-string">'pbport'</span>: <span class="py-src-number">8889</span>,
+ <span class="py-src-string">'ssl'</span>: <span class="py-src-string">'ssl=0'</span> }
+
+<span class="py-src-variable">ser</span> = <span class="py-src-variable">finger</span>.<span class="py-src-variable">makeService</span>(<span class="py-src-variable">options</span>)
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">ser</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/finger/simple-finger.tac"><span class="filename">listings/finger/simple-finger.tac</span></a></div></div>
+
+<pre class="shell" xml:space="preserve">
+% twisted -ny simple-finger.tac
+</pre>
+
+
+<p>Note: the finger <em>user</em> still has ultimate power: he can use
+ <code>makeService</code>, or he can use the lower-level interface if he has
+specific needs (maybe an IRC server on some other port? Maybe we want the
+non-SSL webserver to listen only locally? etc. etc.) This is an important
+design principle: never force a layer of abstraction: allow usage of layers of
+abstractions.</p>
+
+<p>The pasta theory of design:</p>
+
+<ul>
+<li>Spaghetti: each piece of code interacts with every other piece of
+ code [can be implemented with GOTO, functions, objects]</li>
+<li>Lasagna: code has carefully designed layers. Each layer is, in
+ theory independent. However low-level layers usually cannot be
+ used easily, and high-level layers depend on low-level layers.</li>
+<li>Ravioli: each part of the code is useful by itself. There is a thin
+ layer of interfaces between various parts [the sauce]. Each part
+ can be usefully be used elsewhere.</li>
+<li>...but sometimes, the user just wants to order <q>Ravioli</q>, so one
+ coarse-grain easily definable layer of abstraction on top of it all
+ can be useful.</li>
+</ul>
+
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/listings/finger/etc.users b/doc/core/howto/tutorial/listings/finger/etc.users
new file mode 100644
index 0000000..d8c8f8c
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/etc.users
@@ -0,0 +1,2 @@
+moshez: happy and well
+shawn: alive
diff --git a/doc/core/howto/tutorial/listings/finger/finger/__init__.py b/doc/core/howto/tutorial/listings/finger/finger/__init__.py
new file mode 100755
index 0000000..bcb24fa
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger/__init__.py
@@ -0,0 +1,3 @@
+"""
+Finger example application.
+"""
diff --git a/doc/core/howto/tutorial/listings/finger/finger/finger.py b/doc/core/howto/tutorial/listings/finger/finger/finger.py
new file mode 100755
index 0000000..7812af7
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger/finger.py
@@ -0,0 +1,368 @@
+# finger.py module
+
+from zope.interface import Interface, implements
+
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components, log
+from twisted.web import resource, server, xmlrpc
+from twisted.spread import pb
+
+from OpenSSL import SSL
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol.
+ """
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ template = """<html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %(users)s
+ </ul>
+ </body>
+ </html>"""
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+
+ def getChild(self, path, request):
+ if path == '':
+ return self
+ elif path == 'RPC2':
+ return UserStatusXR(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+ def render_GET(self, request):
+ users = self.service.getUsers()
+ def cbUsers(users):
+ request.write(self.template % {'users': ''.join([
+ # Name should be quoted properly these uses.
+ '<li><a href="%s">%s</a></li>' % (name, name)
+ for name in users])})
+ request.finish()
+ users.addCallback(cbUsers)
+ def ebUsers(err):
+ log.err(err, "UserStatusTree failed")
+ request.finish()
+ users.addErrback(ebUsers)
+ return server.NOT_DONE_YET
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ template='''<html><head><title>%(title)s</title></head>
+ <body><h1>%(name)s</h1><p>%(status)s</p></body></html>'''
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ status = self.service.getUser(self.user)
+ def cbStatus(status):
+ request.write(self.template % {
+ 'title': self.user,
+ 'name': self.user,
+ 'status': status})
+ request.finish()
+ status.addCallback(cbStatus)
+ def ebStatus(err):
+ log.err(err, "UserStatus failed")
+ request.finish()
+ status.addErrback(ebStatus)
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class IPerspectiveFinger(Interface):
+
+ def remote_getUser(username):
+ """
+ Return a user's status.
+ """
+
+ def remote_getUsers():
+ """
+ Return a user's status.
+ """
+
+
+class PerspectiveFingerFromService(pb.Root):
+
+ implements(IPerspectiveFinger)
+
+ def __init__(self, service):
+ self.service = service
+
+ def remote_getUser(self, username):
+ return self.service.getUser(username)
+
+ def remote_getUsers(self):
+ return self.service.getUsers()
+
+components.registerAdapter(PerspectiveFingerFromService,
+ IFingerService,
+ IPerspectiveFinger)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+class ServerContextFactory:
+
+ def getContext(self):
+ """
+ Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'.
+ """
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file('server.pem')
+ ctx.use_privatekey_file('server.pem')
+ return ctx
+
+
+
+# Easy configuration
+
+def makeService(config):
+ # finger on port 79
+ s = service.MultiService()
+ f = FingerService(config['file'])
+ h = internet.TCPServer(1079, IFingerFactory(f))
+ h.setServiceParent(s)
+
+
+ # website on port 8000
+ r = resource.IResource(f)
+ r.templateDirectory = config['templates']
+ site = server.Site(r)
+ j = internet.TCPServer(8000, site)
+ j.setServiceParent(s)
+
+ # ssl on port 443
+# if config.get('ssl'):
+# k = internet.SSLServer(443, site, ServerContextFactory())
+# k.setServiceParent(s)
+
+ # irc fingerbot
+ if config.has_key('ircnick'):
+ i = IIRCClientFactory(f)
+ i.nickname = config['ircnick']
+ ircserver = config['ircserver']
+ b = internet.TCPClient(ircserver, 6667, i)
+ b.setServiceParent(s)
+
+ # Pespective Broker on port 8889
+ if config.has_key('pbport'):
+ m = internet.TCPServer(
+ int(config['pbport']),
+ pb.PBServerFactory(IPerspectiveFinger(f)))
+ m.setServiceParent(s)
+
+ return s
diff --git a/doc/core/howto/tutorial/listings/finger/finger/tap.py b/doc/core/howto/tutorial/listings/finger/finger/tap.py
new file mode 100644
index 0000000..a06102c
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger/tap.py
@@ -0,0 +1,20 @@
+# finger/tap.py
+from twisted.application import internet, service
+from twisted.internet import interfaces
+from twisted.python import usage
+import finger
+
+class Options(usage.Options):
+
+ optParameters = [
+ ['file', 'f', '/etc/users'],
+ ['templates', 't', '/usr/share/finger/templates'],
+ ['ircnick', 'n', 'fingerbot'],
+ ['ircserver', None, 'irc.freenode.net'],
+ ['pbport', 'p', 8889],
+ ]
+
+ optFlags = [['ssl', 's']]
+
+def makeService(config):
+ return finger.makeService(config)
diff --git a/doc/core/howto/tutorial/listings/finger/finger01.py b/doc/core/howto/tutorial/listings/finger/finger01.py
new file mode 100755
index 0000000..0561510
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger01.py
@@ -0,0 +1,2 @@
+from twisted.internet import reactor
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger02.py b/doc/core/howto/tutorial/listings/finger/finger02.py
new file mode 100755
index 0000000..e7efbf4
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger02.py
@@ -0,0 +1,10 @@
+from twisted.internet import protocol, reactor
+
+class FingerProtocol(protocol.Protocol):
+ pass
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger03.py b/doc/core/howto/tutorial/listings/finger/finger03.py
new file mode 100755
index 0000000..d323023
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger03.py
@@ -0,0 +1,11 @@
+from twisted.internet import protocol, reactor
+
+class FingerProtocol(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.loseConnection()
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger04.py b/doc/core/howto/tutorial/listings/finger/finger04.py
new file mode 100755
index 0000000..d35f590
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger04.py
@@ -0,0 +1,12 @@
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.loseConnection()
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger05.py b/doc/core/howto/tutorial/listings/finger/finger05.py
new file mode 100755
index 0000000..0d8da8c
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger05.py
@@ -0,0 +1,13 @@
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write("No such user\r\n")
+ self.transport.loseConnection()
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger06.py b/doc/core/howto/tutorial/listings/finger/finger06.py
new file mode 100755
index 0000000..7f78986
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger06.py
@@ -0,0 +1,18 @@
+# Read username, output from empty factory, drop connections
+
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write(self.factory.getUser(user)+"\r\n")
+ self.transport.loseConnection()
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def getUser(self, user):
+ return "No such user"
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger07.py b/doc/core/howto/tutorial/listings/finger/finger07.py
new file mode 100755
index 0000000..cc5dbf1
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger07.py
@@ -0,0 +1,21 @@
+# Read username, output from non-empty factory, drop connections
+
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write(self.factory.getUser(user)+"\r\n")
+ self.transport.loseConnection()
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return self.users.get(user, "No such user")
+
+reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger08.py b/doc/core/howto/tutorial/listings/finger/finger08.py
new file mode 100755
index 0000000..624c5b0
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger08.py
@@ -0,0 +1,30 @@
+# Read username, output from non-empty factory, drop connections
+# Use deferreds, to minimize synchronicity assumptions
+
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger09.py b/doc/core/howto/tutorial/listings/finger/finger09.py
new file mode 100755
index 0000000..336acb3
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger09.py
@@ -0,0 +1,26 @@
+# Read username, output from factory interfacing to OS, drop connections
+
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def getUser(self, user):
+ return utils.getProcessOutput("finger", [user])
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger10.py b/doc/core/howto/tutorial/listings/finger/finger10.py
new file mode 100755
index 0000000..7e4cb93
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger10.py
@@ -0,0 +1,30 @@
+# Read username, output from factory interfacing to web, drop connections
+
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.protocols import basic
+from twisted.web import client
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, prefix):
+ self.prefix=prefix
+
+ def getUser(self, user):
+ return client.getPage(self.prefix+user)
+
+reactor.listenTCP(1079, FingerFactory(prefix='http://livejournal.com/~'))
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger11.tac b/doc/core/howto/tutorial/listings/finger/finger11.tac
new file mode 100755
index 0000000..aae8ca6
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger11.tac
@@ -0,0 +1,34 @@
+# Read username, output from non-empty factory, drop connections
+# Use deferreds, to minimize synchronicity assumptions
+# Write application. Save in 'finger.tpy'
+
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+application = service.Application('finger', uid=1, gid=1)
+factory = FingerFactory(moshez='Happy and well')
+internet.TCPServer(79, factory).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/doc/core/howto/tutorial/listings/finger/finger12.tac b/doc/core/howto/tutorial/listings/finger/finger12.tac
new file mode 100755
index 0000000..69120f1
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger12.tac
@@ -0,0 +1,55 @@
+# But let's try and fix setting away messages, shall we?
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ user = self.lines[0]
+ status = self.lines[1]
+ self.factory.setUser(user, status)
+
+class FingerSetterFactory(protocol.ServerFactory):
+ protocol = FingerSetterProtocol
+
+ def __init__(self, fingerFactory):
+ self.fingerFactory = fingerFactory
+
+ def setUser(self, user, status):
+ self.fingerFactory.users[user] = status
+
+ff = FingerFactory(moshez='Happy and well')
+fsf = FingerSetterFactory(ff)
+
+application = service.Application('finger', uid=1, gid=1)
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79,ff).setServiceParent(serviceCollection)
+internet.TCPServer(1079,fsf).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger13.tac b/doc/core/howto/tutorial/listings/finger/finger13.tac
new file mode 100755
index 0000000..5cf60c9
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger13.tac
@@ -0,0 +1,59 @@
+# Fix asymmetry
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self,reason):
+ user = self.lines[0]
+ status = self.lines[1]
+ self.factory.setUser(user, status)
+
+class FingerService(service.Service):
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def setUser(self, user, status):
+ self.users[user] = status
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getFingerSetterFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerSetterProtocol
+ f.setUser = self.setUser
+ return f
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService(moshez='Happy and well')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79,f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(1079,f.getFingerSetterFactory()
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger14.tac b/doc/core/howto/tutorial/listings/finger/finger14.tac
new file mode 100755
index 0000000..48e4ee0
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger14.tac
@@ -0,0 +1,56 @@
+# Read from file
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.users = {}
+ self.filename = filename
+
+ def _read(self):
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+finger = internet.TCPServer(79, f.getFingerFactory())
+
+finger.setServiceParent(service.IServiceCollection(application))
+f.setServiceParent(service.IServiceCollection(application))
diff --git a/doc/core/howto/tutorial/listings/finger/finger15.tac b/doc/core/howto/tutorial/listings/finger/finger15.tac
new file mode 100755
index 0000000..cf90ddc
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger15.tac
@@ -0,0 +1,87 @@
+# Read from file, announce on the web!
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+from twisted.web import resource, server, static
+import cgi
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+
+class FingerResource(resource.Resource):
+
+ def __init__(self, users):
+ self.users = users
+ resource.Resource.__init__(self)
+
+ # we treat the path as the username
+ def getChild(self, username, request):
+ """
+ 'username' is a string.
+ 'request' is a 'twisted.web.server.Request'.
+ """
+ messagevalue = self.users.get(username)
+ username = cgi.escape(username)
+ if messagevalue is not None:
+ messagevalue = cgi.escape(messagevalue)
+ text = '<h1>%s</h1><p>%s</p>' % (username,messagevalue)
+ else:
+ text = '<h1>%s</h1><p>No such user</p>' % username
+ return static.Data(text, 'text/html')
+
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = FingerResource(self.users)
+ return r
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger16.tac b/doc/core/howto/tutorial/listings/finger/finger16.tac
new file mode 100755
index 0000000..54a12c7
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger16.tac
@@ -0,0 +1,101 @@
+# Read from file, announce on the web, irc
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.web import resource, server, static
+
+import cgi
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+
+class IRCReplyBot(irc.IRCClient):
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ irc.IRCClient.msg(self, user, msg+': '+message)
+ d.addCallback(writeResponse)
+
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('<h1>%s</h1><p>%s</p>' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path,
+ "No such user <p/> usage: site/user")])),
+ 'text/html'))
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
+internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger17.tac b/doc/core/howto/tutorial/listings/finger/finger17.tac
new file mode 100755
index 0000000..ec99041
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger17.tac
@@ -0,0 +1,102 @@
+# Read from file, announce on the web, irc, xml-rpc
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+
+class IRCReplyBot(irc.IRCClient):
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ irc.IRCClient.msg(self, user, msg+': '+message)
+ d.addCallback(writeResponse)
+
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('<h1>%s</h1><p>%s</p>' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path, "No such user")])),
+ 'text/html'))
+ x = xmlrpc.XMLRPC()
+ x.xmlrpc_getUser = self.getUser
+ r.putChild('RPC2', x)
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
+internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger18.tac b/doc/core/howto/tutorial/listings/finger/finger18.tac
new file mode 100755
index 0000000..c39479a
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger18.tac
@@ -0,0 +1,147 @@
+# Do everything properly
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class UserStatusTree(resource.Resource):
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['<li><a href="%s">%s</a></li>' % (user, user)
+ for user in users]
+ return '<ul>'+''.join(l)+'</ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = UserStatusTree(self)
+ x = UserStatusXR(self)
+ r.putChild('RPC2', x)
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
+internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19.tac b/doc/core/howto/tutorial/listings/finger/finger19.tac
new file mode 100755
index 0000000..4b79d63
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19.tac
@@ -0,0 +1,270 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol.
+ """
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['<li><a href="%s">%s</a></li>' % (user, user)
+ for user in users]
+ return '<ul>'+''.join(l)+'</ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19a.tac b/doc/core/howto/tutorial/listings/finger/finger19a.tac
new file mode 100755
index 0000000..e6c66b5
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19a.tac
@@ -0,0 +1,231 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['<li><a href="%s">%s</a></li>' % (user, user)
+ for user in users]
+ return '<ul>'+''.join(l)+'</ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+class MemoryFingerService(service.Service):
+
+ implements([IFingerService, IFingerSetterService])
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def setUser(self, user, status):
+ self.users[user] = status
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = MemoryFingerService(moshez='Happy and well')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(1079, IFingerSetterFactory(f), interface='127.0.0.1'
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19a_changes.py b/doc/core/howto/tutorial/listings/finger/finger19a_changes.py
new file mode 100644
index 0000000..cbb3623
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19a_changes.py
@@ -0,0 +1,29 @@
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+# Advantages of latest version
+
+class MemoryFingerService(service.Service):
+
+ implements([IFingerService, IFingerSetterService])
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def setUser(self, user, status):
+ self.users[user] = status
+
+
+f = MemoryFingerService(moshez='Happy and well')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(1079, IFingerSetterFactory(f), interface='127.0.0.1'
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19b.tac b/doc/core/howto/tutorial/listings/finger/finger19b.tac
new file mode 100755
index 0000000..fdf1675
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19b.tac
@@ -0,0 +1,292 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+import pwd
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol.
+ """
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['<li><a href="%s">%s</a></li>' % (user, user)
+ for user in users]
+ return '<ul>'+''.join(l)+'</ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+# Another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ # need a local finger daemon running for this to work
+ return utils.getProcessOutput("finger", [user])
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = LocalFingerService()
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19b_changes.py b/doc/core/howto/tutorial/listings/finger/finger19b_changes.py
new file mode 100644
index 0000000..3c8ff75
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19b_changes.py
@@ -0,0 +1,19 @@
+
+from twisted.internet import protocol, reactor, defer, utils
+import pwd
+
+# Another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ # need a local finger daemon running for this to work
+ return utils.getProcessOutput("finger", [user])
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+f = LocalFingerService()
diff --git a/doc/core/howto/tutorial/listings/finger/finger19c.tac b/doc/core/howto/tutorial/listings/finger/finger19c.tac
new file mode 100755
index 0000000..98502a5
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19c.tac
@@ -0,0 +1,305 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+import pwd
+import os
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade():
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol.
+ """
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['<li><a href="%s">%s</a></li>' % (user, user)
+ for user in users]
+ return '<ul>'+''.join(l)+'</ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+# Yet another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ user = user.strip()
+ try:
+ entry = pwd.getpwnam(user)
+ except KeyError:
+ return defer.succeed("No such user")
+ try:
+ f = file(os.path.join(entry[5],'.plan'))
+ except (IOError, OSError):
+ return defer.succeed("No such user")
+ data = f.read()
+ data = data.strip()
+ f.close()
+ return defer.succeed(data)
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = LocalFingerService()
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19c_changes.py b/doc/core/howto/tutorial/listings/finger/finger19c_changes.py
new file mode 100644
index 0000000..cc592ea
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19c_changes.py
@@ -0,0 +1,32 @@
+from twisted.internet import protocol, reactor, defer, utils
+import pwd
+import os
+
+
+# Yet another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ user = user.strip()
+ try:
+ entry = pwd.getpwnam(user)
+ except KeyError:
+ return defer.succeed("No such user")
+ try:
+ f = file(os.path.join(entry[5],'.plan'))
+ except (IOError, OSError):
+ return defer.succeed("No such user")
+ data = f.read()
+ data = data.strip()
+ f.close()
+ return defer.succeed(data)
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+
+f = LocalFingerService()
diff --git a/doc/core/howto/tutorial/listings/finger/finger20.tac b/doc/core/howto/tutorial/listings/finger/finger20.tac
new file mode 100755
index 0000000..d29c66f
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger20.tac
@@ -0,0 +1,285 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol.
+ """
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service=service
+
+ # add a specific child for the path "RPC2"
+ self.putChild("RPC2", UserStatusXR(self.service))
+
+ # need to do this for resources at the root of the site
+ self.putChild("", self)
+
+ def _cb_render_GET(self, users, request):
+ userOutput = ''.join(["<li><a href=\"%s\">%s</a></li>" % (user, user)
+ for user in users])
+ request.write("""
+ <html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %s
+ </ul></body></html>""" % userOutput)
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(user=path, service=self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def _cb_render_GET(self, status, request):
+ request.write("""<html><head><title>%s</title></head>
+ <body><h1>%s</h1>
+ <p>%s</p>
+ </body></html>""" % (self.user, self.user, status))
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger21.tac b/doc/core/howto/tutorial/listings/finger/finger21.tac
new file mode 100755
index 0000000..af12354
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger21.tac
@@ -0,0 +1,319 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.spread import pb
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol.
+ """
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service=service
+
+ # add a specific child for the path "RPC2"
+ self.putChild("RPC2", UserStatusXR(self.service))
+
+ # need to do this for resources at the root of the site
+ self.putChild("", self)
+
+ def _cb_render_GET(self, users, request):
+ userOutput = ''.join(["<li><a href=\"%s\">%s</a></li>" % (user, user)
+ for user in users])
+ request.write("""
+ <html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %s
+ </ul></body></html>""" % userOutput)
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(user=path, service=self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def _cb_render_GET(self, status, request):
+ request.write("""<html><head><title>%s</title></head>
+ <body><h1>%s</h1>
+ <p>%s</p>
+ </body></html>""" % (self.user, self.user, status))
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class IPerspectiveFinger(Interface):
+
+ def remote_getUser(username):
+ """
+ Return a user's status.
+ """
+
+ def remote_getUsers():
+ """
+ Return a user's status.
+ """
+
+
+class PerspectiveFingerFromService(pb.Root):
+
+ implements(IPerspectiveFinger)
+
+ def __init__(self, service):
+ self.service = service
+
+ def remote_getUser(self, username):
+ return self.service.getUser(username)
+
+ def remote_getUsers(self):
+ return self.service.getUsers()
+
+components.registerAdapter(PerspectiveFingerFromService,
+ IFingerService,
+ IPerspectiveFinger)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8889, pb.PBServerFactory(IPerspectiveFinger(f))
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger22.py b/doc/core/howto/tutorial/listings/finger/finger22.py
new file mode 100755
index 0000000..4e10fc9
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger22.py
@@ -0,0 +1,337 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.spread import pb
+from zope.interface import Interface, implements
+from OpenSSL import SSL
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol returning a string.
+ """
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(addr):
+ """
+ Return a protocol.
+ """
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service=service
+
+ # add a specific child for the path "RPC2"
+ self.putChild("RPC2", UserStatusXR(self.service))
+
+ # need to do this for resources at the root of the site
+ self.putChild("", self)
+
+ def _cb_render_GET(self, users, request):
+ userOutput = ''.join(["<li><a href=\"%s\">%s</a></li>" % (user, user)
+ for user in users])
+ request.write("""
+ <html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %s
+ </ul></body></html>""" % userOutput)
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(user=path, service=self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def _cb_render_GET(self, status, request):
+ request.write("""<html><head><title>%s</title></head>
+ <body><h1>%s</h1>
+ <p>%s</p>
+ </body></html>""" % (self.user, self.user, status))
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class IPerspectiveFinger(Interface):
+
+ def remote_getUser(username):
+ """
+ Return a user's status.
+ """
+
+ def remote_getUsers():
+ """
+ Return a user's status.
+ """
+
+class PerspectiveFingerFromService(pb.Root):
+
+ implements(IPerspectiveFinger)
+
+ def __init__(self, service):
+ self.service = service
+
+ def remote_getUser(self, username):
+ return self.service.getUser(username)
+
+ def remote_getUsers(self):
+ return self.service.getUsers()
+
+components.registerAdapter(PerspectiveFingerFromService,
+ IFingerService,
+ IPerspectiveFinger)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+class ServerContextFactory:
+
+ def getContext(self):
+ """
+ Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'.
+ """
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file('server.pem')
+ ctx.use_privatekey_file('server.pem')
+ return ctx
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+site = server.Site(resource.IResource(f))
+internet.TCPServer(8000, site
+ ).setServiceParent(serviceCollection)
+internet.SSLServer(443, site, ServerContextFactory()
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8889, pb.PBServerFactory(IPerspectiveFinger(f))
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/fingerPBclient.py b/doc/core/howto/tutorial/listings/finger/fingerPBclient.py
new file mode 100755
index 0000000..66ed0ae
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/fingerPBclient.py
@@ -0,0 +1,26 @@
+# test the PB finger on port 8889
+# this code is essentially the same as
+# the first example in howto/pb-usage
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def gotObject(object):
+ print "got object:", object
+ object.callRemote("getUser","moshez").addCallback(gotData)
+# or
+# object.callRemote("getUsers").addCallback(gotData)
+
+def gotData(data):
+ print 'server sent:', data
+ reactor.stop()
+
+def gotNoObject(reason):
+ print "no object:",reason
+ reactor.stop()
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("127.0.0.1",8889, factory)
+factory.getRootObject().addCallbacks(gotObject,gotNoObject)
+reactor.run()
+
diff --git a/doc/core/howto/tutorial/listings/finger/fingerXRclient.py b/doc/core/howto/tutorial/listings/finger/fingerXRclient.py
new file mode 100755
index 0000000..b854bcf
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/fingerXRclient.py
@@ -0,0 +1,5 @@
+# testing xmlrpc finger
+
+import xmlrpclib
+server = xmlrpclib.Server('http://127.0.0.1:8000/RPC2')
+print server.getUser('moshez')
diff --git a/doc/core/howto/tutorial/listings/finger/finger_config.py b/doc/core/howto/tutorial/listings/finger/finger_config.py
new file mode 100644
index 0000000..226a26a
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger_config.py
@@ -0,0 +1,38 @@
+# Easy configuration
+# makeService from finger module
+
+def makeService(config):
+ # finger on port 79
+ s = service.MultiService()
+ f = FingerService(config['file'])
+ h = internet.TCPServer(79, IFingerFactory(f))
+ h.setServiceParent(s)
+
+ # website on port 8000
+ r = resource.IResource(f)
+ r.templateDirectory = config['templates']
+ site = server.Site(r)
+ j = internet.TCPServer(8000, site)
+ j.setServiceParent(s)
+
+ # ssl on port 443
+ if config.get('ssl'):
+ k = internet.SSLServer(443, site, ServerContextFactory())
+ k.setServiceParent(s)
+
+ # irc fingerbot
+ if config.has_key('ircnick'):
+ i = IIRCClientFactory(f)
+ i.nickname = config['ircnick']
+ ircserver = config['ircserver']
+ b = internet.TCPClient(ircserver, 6667, i)
+ b.setServiceParent(s)
+
+ # Pespective Broker on port 8889
+ if config.has_key('pbport'):
+ m = internet.TCPServer(
+ int(config['pbport']),
+ pb.PBServerFactory(IPerspectiveFinger(f)))
+ m.setServiceParent(s)
+
+ return s
diff --git a/doc/core/howto/tutorial/listings/finger/fingerproxy.tac b/doc/core/howto/tutorial/listings/finger/fingerproxy.tac
new file mode 100644
index 0000000..839c63d
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/fingerproxy.tac
@@ -0,0 +1,110 @@
+# finger proxy
+from twisted.application import internet, service
+from twisted.internet import defer, protocol, reactor
+from twisted.protocols import basic
+from twisted.python import components
+from zope.interface import Interface, implements
+
+
+def catchError(err):
+ return "Internal error in server"
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+
+class FingerFactoryFromService(protocol.ClientFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerClient(protocol.Protocol):
+
+ def connectionMade(self):
+ self.transport.write(self.factory.user+"\r\n")
+ self.buf = []
+
+ def dataReceived(self, data):
+ self.buf.append(data)
+
+ def connectionLost(self, reason):
+ self.factory.gotData(''.join(self.buf))
+
+class FingerClientFactory(protocol.ClientFactory):
+
+ protocol = FingerClient
+
+ def __init__(self, user):
+ self.user = user
+ self.d = defer.Deferred()
+
+ def clientConnectionFailed(self, _, reason):
+ self.d.errback(reason)
+
+ def gotData(self, data):
+ self.d.callback(data)
+
+
+def finger(user, host, port=79):
+ f = FingerClientFactory(user)
+ reactor.connectTCP(host, port, f)
+ return f.d
+
+
+class ProxyFingerService(service.Service):
+ implements(IFingerService)
+
+ def getUser(self, user):
+ try:
+ user, host = user.split('@', 1)
+ except:
+ user = user.strip()
+ host = '127.0.0.1'
+ ret = finger(user, host)
+ ret.addErrback(lambda _: "Could not connect to remote host")
+ return ret
+
+ def getUsers(self):
+ return defer.succeed([])
+
+application = service.Application('finger', uid=1, gid=1)
+f = ProxyFingerService()
+internet.TCPServer(7779, IFingerFactory(f)).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/doc/core/howto/tutorial/listings/finger/organized-finger.tac b/doc/core/howto/tutorial/listings/finger/organized-finger.tac
new file mode 100644
index 0000000..2f9a129
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/organized-finger.tac
@@ -0,0 +1,31 @@
+# organized-finger.tac
+# eg: twistd -ny organized-finger.tac
+
+import finger
+
+from twisted.internet import protocol, reactor, defer
+from twisted.spread import pb
+from twisted.web import resource, server
+from twisted.application import internet, service, strports
+from twisted.python import log
+
+application = service.Application('finger', uid=1, gid=1)
+f = finger.FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, finger.IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+
+site = server.Site(resource.IResource(f))
+internet.TCPServer(8000, site
+ ).setServiceParent(serviceCollection)
+
+internet.SSLServer(443, site, finger.ServerContextFactory()
+ ).setServiceParent(serviceCollection)
+
+i = finger.IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+
+internet.TCPServer(8889, pb.PBServerFactory(finger.IPerspectiveFinger(f))
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/simple-finger.tac b/doc/core/howto/tutorial/listings/finger/simple-finger.tac
new file mode 100644
index 0000000..2e75cb1
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/simple-finger.tac
@@ -0,0 +1,17 @@
+# simple-finger.tac
+# eg: twistd -ny simple-finger.tac
+
+from twisted.application import service
+
+import finger
+
+options = { 'file': '/etc/users',
+ 'templates': '/usr/share/finger/templates',
+ 'ircnick': 'fingerbot',
+ 'ircserver': 'irc.freenode.net',
+ 'pbport': 8889,
+ 'ssl': 'ssl=0' }
+
+ser = finger.makeService(options)
+application = service.Application('finger', uid=1, gid=1)
+ser.setServiceParent(service.IServiceCollection(application))
diff --git a/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py b/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py
new file mode 100644
index 0000000..73361ae
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py
@@ -0,0 +1,5 @@
+
+from twisted.application.service import ServiceMaker
+
+finger = ServiceMaker(
+ 'finger', 'finger.tap', 'Run a finger service', 'finger')
diff --git a/doc/core/howto/tutorial/pb.html b/doc/core/howto/tutorial/pb.html
new file mode 100644
index 0000000..5cdfb04
--- /dev/null
+++ b/doc/core/howto/tutorial/pb.html
@@ -0,0 +1,728 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: Twisted client support using Perspective Broker</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: Twisted client support using Perspective Broker</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Use Perspective Broker</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the seventh part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we add a Perspective Broker service to the finger application
+so that Twisted clients can access the finger server. Perspective Broker is
+introduced in depth in its own <a href="../index.html#pb" shape="rect">section</a> of the
+core howto index.</p>
+
+<h2>Use Perspective Broker<a name="auto1"/></h2>
+
+<p>We add support for perspective broker, Twisted's native remote object
+protocol. Now, Twisted clients will not have to go through XML-RPCish
+contortions to get information about users.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">microdom</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a list of strings.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Set the user's status to something.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>=<span class="py-src-variable">service</span>
+
+ <span class="py-src-comment"># add a specific child for the path &quot;RPC2&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;RPC2&quot;</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-comment"># need to do this for resources at the root of the site</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;&quot;</span>, <span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">users</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">userOutput</span> = <span class="py-src-string">''</span>.<span class="py-src-variable">join</span>([<span class="py-src-string">&quot;&lt;li&gt;&lt;a href=\&quot;%s\&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;&quot;</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>])
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;
+ &lt;html&gt;&lt;head&gt;&lt;title&gt;Users&lt;/title&gt;&lt;/head&gt;&lt;body&gt;
+ &lt;h1&gt;Users&lt;/h1&gt;
+ &lt;ul&gt;
+ %s
+ &lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % <span class="py-src-variable">userOutput</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">user</span>=<span class="py-src-variable">path</span>, <span class="py-src-variable">service</span>=<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">status</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;&lt;html&gt;&lt;head&gt;&lt;title&gt;%s&lt;/title&gt;&lt;/head&gt;
+ &lt;body&gt;&lt;h1&gt;%s&lt;/h1&gt;
+ &lt;p&gt;%s&lt;/p&gt;
+ &lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>))
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IPerspectiveFinger</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">username</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a user's status.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a user's status.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PerspectiveFingerFromService</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPerspectiveFinger</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">username</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">PerspectiveFingerFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IPerspectiveFinger</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">f</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8889</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">IPerspectiveFinger</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger21.tac"><span class="filename">listings/finger/finger21.tac</span></a></div></div>
+
+<p>A simple client to test the perspective broker finger:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-comment"># test the PB finger on port 8889</span>
+<span class="py-src-comment"># this code is essentially the same as</span>
+<span class="py-src-comment"># the first example in howto/pb-usage</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">gotObject</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got object:&quot;</span>, <span class="py-src-variable">object</span>
+ <span class="py-src-variable">object</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;getUser&quot;</span>,<span class="py-src-string">&quot;moshez&quot;</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">gotData</span>)
+<span class="py-src-comment"># or</span>
+<span class="py-src-comment"># object.callRemote(&quot;getUsers&quot;).addCallback(gotData)</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">gotData</span>(<span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'server sent:'</span>, <span class="py-src-variable">data</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">gotNoObject</span>(<span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;no object:&quot;</span>,<span class="py-src-variable">reason</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;127.0.0.1&quot;</span>,<span class="py-src-number">8889</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>().<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">gotObject</span>,<span class="py-src-variable">gotNoObject</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/fingerPBclient.py"><span class="filename">listings/finger/fingerPBclient.py</span></a></div></div>
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/protocol.html b/doc/core/howto/tutorial/protocol.html
new file mode 100644
index 0000000..d3915bb
--- /dev/null
+++ b/doc/core/howto/tutorial/protocol.html
@@ -0,0 +1,1121 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: adding features to the finger service</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: adding features to the finger service</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Setting Message By Local Users</a></li><li><a href="#auto2">Use Services to Make Dependencies Sane</a></li><li><a href="#auto3">Read Status File</a></li><li><a href="#auto4">Announce on Web, Too</a></li><li><a href="#auto5">Announce on IRC, Too</a></li><li><a href="#auto6">Add XML-RPC Support</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the second part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this section of the tutorial, our finger server will continue to sprout
+features: the ability for users to set finger announces, and using our finger
+service to send those announcements on the web, on IRC and over XML-RPC.
+Resources and XML-RPC are introduced in the Web Applications portion of
+the <a href="../../../web/howto/index.html" shape="rect">Twisted Web howto</a>. More examples
+using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.words.protocols.irc.html" title="twisted.words.protocols.irc">twisted.words.protocols.irc</a></code> can be found
+in <a href="../clients.html" shape="rect">Writing a TCP Client</a> and
+the <a href="../../../words/examples/index.html" shape="rect">Twisted Words examples</a>.</p>
+
+<h2>Setting Message By Local Users<a name="auto1"/></h2>
+
+<p>Now that port 1079 is free, maybe we can use it with a different
+server, one which will let people set their messages. It does
+no access control, so anyone who can login to the machine can
+set any message. We assume this is the desired behavior in
+our case. Testing it can be done by simply:
+</p>
+
+<pre class="shell" xml:space="preserve">
+% nc localhost 1079 # or telnet localhost 1079
+moshez
+Giving a tutorial now, sorry!
+^D
+</pre>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+</p><span class="py-src-comment"># But let's try and fix setting away messages, shall we?</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>[<span class="py-src-number">1</span>]
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">fingerFactory</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">fingerFactory</span> = <span class="py-src-variable">fingerFactory</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">fingerFactory</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+
+<span class="py-src-variable">ff</span> = <span class="py-src-variable">FingerFactory</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>)
+<span class="py-src-variable">fsf</span> = <span class="py-src-variable">FingerSetterFactory</span>(<span class="py-src-variable">ff</span>)
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>,<span class="py-src-variable">ff</span>).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">1079</span>,<span class="py-src-variable">fsf</span>).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger12.tac"><span class="filename">listings/finger/finger12.tac</span></a></div></div>
+
+<p>This program has two protocol-factory-TCPServer pairs, which are
+both child services of the application. Specifically,
+the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.Service.setServiceParent.html" title="twisted.application.service.Service.setServiceParent">setServiceParent</a></code>
+method is used to define the two TCPServer services as children
+of <code>application</code>, which implements <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IServiceCollection.html" title="twisted.application.service.IServiceCollection">IServiceCollection</a></code>. Both
+services are thus started with the application.</p>
+
+
+<h2>Use Services to Make Dependencies Sane<a name="auto2"/></h2>
+
+<p>The previous version had the setter poke at the innards of the
+finger factory. This strategy is usually not a good idea: this version makes
+both factories symmetric by making them both look at a single
+object. Services are useful for when an object is needed which is
+not related to a specific network server. Here, we define a common service
+class with methods that will create factories on the fly. The service
+also contains methods the factories will depend on.</p>
+
+<p>The factory-creation methods, <code>getFingerFactory</code>
+and <code>getFingerSetterFactory</code>, follow this pattern:</p>
+
+<ol>
+
+<li>Instantiate a generic server
+factory, <code>twisted.internet.protocol.ServerFactory</code>.</li>
+
+<li>Set the protocol class, just like our factory class would have.</li>
+
+<li>Copy a service method to the factory as a function attribute. The
+function won't have access to the factory's <code>self</code>, but
+that's OK because as a bound method it has access to the
+service's <code>self</code>, which is what it needs.
+For <code>getUser</code>, a custom method defined in the service gets
+copied. For <code>setUser</code>, a standard method of
+the <code>users</code> dictionary is copied.</li>
+
+
+</ol>
+
+<p>Thus, we stopped subclassing: the service simply puts useful methods and
+attributes inside the factories. We are getting better at protocol design:
+none of our protocol classes had to be changed, and neither will have to
+change until the end of the tutorial.</p>
+
+<p>As an application service, this new finger service implements the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code> interface and
+can be started and stopped in a standardized manner. We'll make use of this in
+the next example.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+</p><span class="py-src-comment"># Fix asymmetry</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>,<span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>[<span class="py-src-number">1</span>]
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerSetterFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">setUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">setUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>,<span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">1079</span>,<span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerSetterFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger13.tac"><span class="filename">listings/finger/finger13.tac</span></a></div></div>
+
+<p>Most application services will want to use the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.Service.html" title="twisted.application.service.Service">Service</a></code> base class, which implements
+all the generic <code>IService</code> behavior.</p>
+
+<h2>Read Status File<a name="auto3"/></h2>
+
+<p>This version shows how, instead of just letting users set their
+messages, we can read those from a centrally managed file. We cache
+results, and every 30 seconds we refresh it. Services are useful
+for such scheduled tasks.</p>
+
+<div class="listing"><pre>
+moshez: happy and well
+shawn: alive
+</pre><div class="caption">sample /etc/users file - <a href="listings/finger/etc.users"><span class="filename">listings/finger/etc.users</span></a></div></div>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+</p><span class="py-src-comment"># Read from file</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">finger</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>())
+
+<span class="py-src-variable">finger</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+<span class="py-src-variable">f</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/finger/finger14.tac"><span class="filename">listings/finger/finger14.tac</span></a></div></div>
+
+<p>Since this version is reading data from a file (and refreshing the data
+every 30 seconds), there is no <code>FingerSetterFactory</code> and thus
+nothing listening on port 1079.</p>
+
+<p>Here we override the standard <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.Service.startService.html" title="twisted.application.service.Service.startService">startService</a></code>
+and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.application.service.Service.stopService.html" title="twisted.application.service.Service.stopService">stopService</a></code> hooks in
+the Finger service, which is set up as a child service of the
+application in the last line of the code. <code>startService</code>
+calls <code>_read</code>, the function responsible for reading the
+data; <code>reactor.callLater</code> is then used to schedule it to
+run again after thirty seconds every time it is
+called. <code>reactor.callLater</code> returns an object that lets us
+cancel the scheduled run in <code>stopService</code> using
+its <code>cancel</code> method.</p>
+
+<h2>Announce on Web, Too<a name="auto4"/></h2>
+
+<p>The same kind of service can also produce things useful for other
+protocols. For example, in twisted.web, the factory itself
+(<code base="API" class="twisted.web.server">Site</code>) is almost
+never subclassed — instead, it is given a resource, which
+represents the tree of resources available via URLs. That hierarchy is
+navigated by <code base="API" class="twisted.web.server">Site</code>
+and overriding it dynamically is possible with <code base="API" class="twisted.web.resource.Resource">getChild</code>.</p>
+
+<p>To integrate this into the Finger application (just because we can), we set
+up a new TCPServer that calls the <code base="API" class="twisted.web.server">Site</code> factory and retrieves resources via a
+new function of <code>FingerService</code> named <code>getResource</code>.
+This function specifically returns a <code base="API" class="twisted.web.resource">Resource</code> object with an overridden <code base="API" class="twisted.web.resource.Resource">getChild</code> method.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+</p><span class="py-src-comment"># Read from file, announce on the web!</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerResource</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">users</span>
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-comment"># we treat the path as the username</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ 'username' is a string.
+ 'request' is a 'twisted.web.server.Request'.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">messagevalue</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">username</span>)
+ <span class="py-src-variable">username</span> = <span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>(<span class="py-src-variable">username</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">messagevalue</span> <span class="py-src-keyword">is</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">None</span>:
+ <span class="py-src-variable">messagevalue</span> = <span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>(<span class="py-src-variable">messagevalue</span>)
+ <span class="py-src-variable">text</span> = <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;&lt;p&gt;%s&lt;/p&gt;'</span> % (<span class="py-src-variable">username</span>,<span class="py-src-variable">messagevalue</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">text</span> = <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;&lt;p&gt;No such user&lt;/p&gt;'</span> % <span class="py-src-variable">username</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">static</span>.<span class="py-src-variable">Data</span>(<span class="py-src-variable">text</span>, <span class="py-src-string">'text/html'</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getResource</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">FingerResource</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">r</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">f</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">f</span>.<span class="py-src-variable">getResource</span>())
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger15.tac"><span class="filename">listings/finger/finger15.tac</span></a></div></div>
+
+
+<h2>Announce on IRC, Too<a name="auto5"/></h2>
+
+<p>This is the first time there is client code. IRC clients often act a lot like
+servers: responding to events from the network. The reconnecting client factory
+will make sure that severed links will get re-established, with intelligent
+tweaked exponential back-off algorithms. The IRC client itself is simple: the
+only real hack is getting the nickname from the factory
+in <code>connectionMade</code>.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+</p><span class="py-src-comment"># Read from file, announce on the web, irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">user</span>, <span class="py-src-variable">msg</span>+<span class="py-src-string">': '</span>+<span class="py-src-variable">message</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getResource</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>()
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">getChild</span> = (<span class="py-src-keyword">lambda</span> <span class="py-src-variable">path</span>, <span class="py-src-variable">request</span>:
+ <span class="py-src-variable">static</span>.<span class="py-src-variable">Data</span>(<span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;&lt;p&gt;%s&lt;/p&gt;'</span> %
+ <span class="py-src-variable">tuple</span>(<span class="py-src-variable">map</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>,
+ [<span class="py-src-variable">path</span>,<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">path</span>,
+ <span class="py-src-string">&quot;No such user &lt;p/&gt; usage: site/user&quot;</span>)])),
+ <span class="py-src-string">'text/html'</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">r</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getIRCBot</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">nickname</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ReconnectingClientFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">f</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">f</span>.<span class="py-src-variable">getResource</span>())
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getIRCBot</span>(<span class="py-src-string">'fingerbot'</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger16.tac"><span class="filename">listings/finger/finger16.tac</span></a></div></div>
+
+<p><code>FingerService</code> now has another new
+function, <code>getIRCbot</code>, which returns
+the <code>ReconnectingClientFactory</code>. This factory in turn will
+instantiate the <code>IRCReplyBot</code> protocol. The IRCBot is
+configured in the last line to connect
+to <code>irc.freenode.org</code> with a nickname
+of <code>fingerbot</code>.</p>
+
+<p>By
+overriding <code>irc.IRCClient.connectionMade</code>, <code>IRCReplyBot</code>
+can access the <code>nickname</code> attribute of the factory that
+instantiated it.</p>
+
+<h2>Add XML-RPC Support<a name="auto6"/></h2>
+
+<p>In Twisted, XML-RPC support is handled just as though it was
+another resource. That resource will still support GET calls normally
+through render(), but that is usually left unimplemented. Note
+that it is possible to return deferreds from XML-RPC methods.
+The client, of course, will not get the answer until the deferred
+is triggered.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+</p><span class="py-src-comment"># Read from file, announce on the web, irc, xml-rpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">user</span>, <span class="py-src-variable">msg</span>+<span class="py-src-string">': '</span>+<span class="py-src-variable">message</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getResource</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>()
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">getChild</span> = (<span class="py-src-keyword">lambda</span> <span class="py-src-variable">path</span>, <span class="py-src-variable">request</span>:
+ <span class="py-src-variable">static</span>.<span class="py-src-variable">Data</span>(<span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;&lt;p&gt;%s&lt;/p&gt;'</span> %
+ <span class="py-src-variable">tuple</span>(<span class="py-src-variable">map</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>,
+ [<span class="py-src-variable">path</span>,<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">path</span>, <span class="py-src-string">&quot;No such user&quot;</span>)])),
+ <span class="py-src-string">'text/html'</span>))
+ <span class="py-src-variable">x</span> = <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>()
+ <span class="py-src-variable">x</span>.<span class="py-src-variable">xmlrpc_getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">x</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">r</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getIRCBot</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">nickname</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ReconnectingClientFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">f</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">f</span>.<span class="py-src-variable">getResource</span>())
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getIRCBot</span>(<span class="py-src-string">'fingerbot'</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger17.tac"><span class="filename">listings/finger/finger17.tac</span></a></div></div>
+
+<p>Instead of a web browser, we can test the XMLRPC finger using a simple
+client based on Python's built-in <code>xmlrpclib</code>, which will access
+the resource we've made available at <code>localhost/RPC2</code>.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-comment"># testing xmlrpc finger</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpclib</span>
+<span class="py-src-variable">server</span> = <span class="py-src-variable">xmlrpclib</span>.<span class="py-src-variable">Server</span>(<span class="py-src-string">'http://127.0.0.1:8000/RPC2'</span>)
+<span class="py-src-keyword">print</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-string">'moshez'</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/fingerXRclient.py"><span class="filename">listings/finger/fingerXRclient.py</span></a></div></div>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/style.html b/doc/core/howto/tutorial/style.html
new file mode 100644
index 0000000..8cc9969
--- /dev/null
+++ b/doc/core/howto/tutorial/style.html
@@ -0,0 +1,333 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: cleaning up the finger code</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: cleaning up the finger code</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Write Readable Code</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the third part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this section of the tutorial, we'll clean up our code so that it is
+closer to a readable and extensible style.</p>
+
+<h2>Write Readable Code<a name="auto1"/></h2>
+
+<p>The last version of the application had a lot of hacks. We avoided
+sub-classing, didn't support things like user listings over the web,
+and removed all blank lines -- all in the interest of code
+which is shorter. Here we take a step back, subclass what is more
+naturally a subclass, make things which should take multiple lines
+take them, etc. This shows a much better style of developing Twisted
+applications, though the hacks in the previous stages are sometimes
+used in throw-away prototypes.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+</p><span class="py-src-comment"># Do everything properly</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">formatUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">l</span> = [<span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;ul&gt;'</span>+<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">l</span>)+<span class="py-src-string">'&lt;/ul&gt;'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">formatUsers</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>==<span class="py-src-string">&quot;&quot;</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>:
+ <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;'</span>%<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">'&lt;p&gt;%s&lt;/p&gt;'</span>%<span class="py-src-variable">m</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getResource</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">x</span> = <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">x</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">r</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getIRCBot</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">nickname</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ReconnectingClientFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">f</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">f</span>.<span class="py-src-variable">getResource</span>())
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getIRCBot</span>(<span class="py-src-string">'fingerbot'</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger18.tac"><span class="filename">listings/finger/finger18.tac</span></a></div></div>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/tutorial/web.html b/doc/core/howto/tutorial/web.html
new file mode 100644
index 0000000..fca15b7
--- /dev/null
+++ b/doc/core/howto/tutorial/web.html
@@ -0,0 +1,610 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: a web frontend</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: a web frontend</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the sixth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we demonstrate adding a web frontend using
+simple <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">twisted.web.resource.Resource</a></code>
+objects: <code class="python">UserStatusTree</code>, which will
+produce a listing of all users at the base URL (<code>/</code>) of our
+site; <code class="python">UserStatus</code>, which gives the status
+of each user at the location <code>/username</code>;
+and <code class="python">UserStatusXR</code>, which exposes an XMLRPC
+interface to <code class="python">getUser</code>
+and <code class="python">getUsers</code> functions at the
+URL <code>/RPC2</code>.</p>
+
+<p>In this example we construct HTML segments manually. If the web interface
+was less trivial, we would want to use more sophisticated web templating and
+design our system so that HTML rendering and logic were clearly separated.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">microdom</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a list of strings.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Set the user's status to something.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol returning a string.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a deferred returning a string.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a protocol.
+ &quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>=<span class="py-src-variable">service</span>
+
+ <span class="py-src-comment"># add a specific child for the path &quot;RPC2&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;RPC2&quot;</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-comment"># need to do this for resources at the root of the site</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;&quot;</span>, <span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">users</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">userOutput</span> = <span class="py-src-string">''</span>.<span class="py-src-variable">join</span>([<span class="py-src-string">&quot;&lt;li&gt;&lt;a href=\&quot;%s\&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;&quot;</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>])
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;
+ &lt;html&gt;&lt;head&gt;&lt;title&gt;Users&lt;/title&gt;&lt;/head&gt;&lt;body&gt;
+ &lt;h1&gt;Users&lt;/h1&gt;
+ &lt;ul&gt;
+ %s
+ &lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % <span class="py-src-variable">userOutput</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">user</span>=<span class="py-src-variable">path</span>, <span class="py-src-variable">service</span>=<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">status</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;&lt;html&gt;&lt;head&gt;&lt;title&gt;%s&lt;/title&gt;&lt;/head&gt;
+ &lt;body&gt;&lt;h1&gt;%s&lt;/h1&gt;
+ &lt;p&gt;%s&lt;/p&gt;
+ &lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>))
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">f</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger20.tac"><span class="filename">listings/finger/finger20.tac</span></a></div></div>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/udp.html b/doc/core/howto/udp.html
new file mode 100644
index 0000000..756d3d1
--- /dev/null
+++ b/doc/core/howto/udp.html
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: UDP Networking</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">UDP Networking</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">DatagramProtocol</a></li><li><a href="#auto2">Connected UDP</a></li><li><a href="#auto3">Multicast UDP</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>Unlike TCP, UDP has no notion of connections. A UDP socket can receive
+ datagrams from any server on the network and send datagrams to any host on
+ the network. In addition, datagrams may arrive in any order, never arrive at
+ all, or be duplicated in transit.</p>
+
+ <p>Since there are no connections, we only use a single object, a protocol,
+ for each UDP socket. We then use the reactor to connect this protocol to a
+ UDP transport, using the
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUDP.html" title="twisted.internet.interfaces.IReactorUDP">twisted.internet.interfaces.IReactorUDP</a></code>
+ reactor API.</p>
+
+ <h2>DatagramProtocol<a name="auto1"/></h2>
+
+ <p>The class where you actually implement the protocol parsing and handling
+ will usually be descended
+ from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.DatagramProtocol.html" title="twisted.internet.protocol.DatagramProtocol">twisted.internet.protocol.DatagramProtocol</a></code> or
+ from one of its convenience children. The <code>DatagramProtocol</code>
+ class receives datagrams and can send them out over the network. Received
+ datagrams include the address they were sent from. When sending datagrams
+ the destination address must be specified.</p>
+
+ <p>Here is a simple example:</p>
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DatagramProtocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">DatagramProtocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">datagramReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>, (<span class="py-src-parameter">host</span>, <span class="py-src-parameter">port</span>)):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;received %r from %s:%d&quot;</span> % (<span class="py-src-variable">data</span>, <span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>, (<span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>))
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenUDP</span>(<span class="py-src-number">9999</span>, <span class="py-src-variable">Echo</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>As you can see, the protocol is registered with the reactor. This means
+ it may be persisted if it's added to an application, and thus it has
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.AbstractDatagramProtocol.startProtocol.html" title="twisted.internet.protocol.AbstractDatagramProtocol.startProtocol">startProtocol</a></code>
+ and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.protocol.AbstractDatagramProtocol.stopProtocol.html" title="twisted.internet.protocol.AbstractDatagramProtocol.stopProtocol">stopProtocol</a></code>
+ methods that will get called when the protocol is connected and disconnected
+ from a UDP socket.</p>
+
+ <p>The protocol's <code class="python">transport</code> attribute will
+ implement the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IUDPTransport.html" title="twisted.internet.interfaces.IUDPTransport">twisted.internet.interfaces.IUDPTransport</a></code> interface.
+ Notice that the <code class="python">host</code> argument should be an
+ IP address, not a hostname. If you only have the hostname use <code class="python">reactor.resolve()</code> to resolve the address (see <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorCore.resolve.html" title="twisted.internet.interfaces.IReactorCore.resolve">twisted.internet.interfaces.IReactorCore.resolve</a></code>).</p>
+
+
+ <h2>Connected UDP<a name="auto2"/></h2>
+
+ <p>A connected UDP socket is slightly different from a standard one - it
+ can only send and receive datagrams to/from a single address, but this
+ does not in any way imply a connection. Datagrams may still arrive in any
+ order, and the port on the other side may have no one listening. The
+ benefit of the connected UDP socket is that it it <strong>may</strong>
+ provide notification of undelivered packages. This depends on many
+ factors, almost all of which are out of the control of the application,
+ but it still presents certain benefits which occasionally make it
+ useful.</p>
+
+ <p>Unlike a regular UDP protocol, we do not need to specify where to send
+ datagrams and are not told where they came from since they can only come
+ from the address to which the socket is 'connected'.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DatagramProtocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Helloer</span>(<span class="py-src-parameter">DatagramProtocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startProtocol</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">host</span> = <span class="py-src-string">&quot;192.168.1.1&quot;</span>
+ <span class="py-src-variable">port</span> = <span class="py-src-number">1234</span>
+
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">connect</span>(<span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;now we can only send to host %s port %d&quot;</span> % (<span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;hello&quot;</span>) <span class="py-src-comment"># no need for address</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">datagramReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>, (<span class="py-src-parameter">host</span>, <span class="py-src-parameter">port</span>)):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;received %r from %s:%d&quot;</span> % (<span class="py-src-variable">data</span>, <span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>)
+
+ <span class="py-src-comment"># Possibly invoked if there is no server listening on the</span>
+ <span class="py-src-comment"># address to which we are sending.</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionRefused</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;No one listening&quot;</span>
+
+<span class="py-src-comment"># 0 means any port, we don't care in this case</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenUDP</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">Helloer</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Note that <code class="python">connect()</code>,
+ like <code class="python">write()</code> will only accept IP addresses, not
+ unresolved hostnames. To obtain the IP of a hostname
+ use <code class="python">reactor.resolve()</code>, e.g.:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">gotIP</span>(<span class="py-src-parameter">ip</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;IP of 'example.com' is&quot;</span>, <span class="py-src-variable">ip</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">3</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">resolve</span>(<span class="py-src-string">'example.com'</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">gotIP</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Connecting to a new address after a previous connection or making a
+ connected port unconnected are not currently supported, but likely will be
+ in the future.</p>
+
+ <h2>Multicast UDP<a name="auto3"/></h2>
+
+ <p>Multicast allows a process to contact multiple hosts with a single
+ packet, without knowing the specific IP address of any of the hosts. This
+ is in contrast to normal, or unicast, UDP, where each datagram has a single
+ IP as its destination. Multicast datagrams are sent to special multicast
+ group addresses (in the IPv4 range 224.0.0.0 to 239.255.255.255), along with
+ a corresponding port. In order to receive multicast datagrams, you must
+ join that specific group address. However, any UDP socket can send to
+ multicast addresses.</p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DatagramProtocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MulticastPingPong</span>(<span class="py-src-parameter">DatagramProtocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startProtocol</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Called after protocol has started listening.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-comment"># Set the TTL&gt;1 so multicast will cross router hops:</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">setTTL</span>(<span class="py-src-number">5</span>)
+ <span class="py-src-comment"># Join a specific multicast group:</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">joinGroup</span>(<span class="py-src-string">&quot;228.0.0.5&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">datagramReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">datagram</span>, <span class="py-src-parameter">address</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Datagram %s received from %s&quot;</span> % (<span class="py-src-variable">repr</span>(<span class="py-src-variable">datagram</span>), <span class="py-src-variable">repr</span>(<span class="py-src-variable">address</span>))
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">datagram</span> == <span class="py-src-string">&quot;Client: Ping&quot;</span>:
+ <span class="py-src-comment"># Rather than replying to the group multicast address, we send the</span>
+ <span class="py-src-comment"># reply directly (unicast) to the originating port:</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Server: Pong&quot;</span>, <span class="py-src-variable">address</span>)
+
+
+<span class="py-src-comment"># We use listenMultiple=True so that we can run MulticastServer.py and</span>
+<span class="py-src-comment"># MulticastClient.py on same machine:</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenMulticast</span>(<span class="py-src-number">8005</span>, <span class="py-src-variable">MulticastPingPong</span>(),
+ <span class="py-src-variable">listenMultiple</span>=<span class="py-src-variable">True</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/udp/MulticastServer.py"><span class="filename">listings/udp/MulticastServer.py</span></a></div></div>
+
+ <p>As with UDP, with multicast there is no server/client differentiation
+ at the protocol level. Our server example is very simple and closely
+ resembles a normal <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorUDP.listenUDP.html" title="twisted.internet.interfaces.IReactorUDP.listenUDP">listenUDP</a></code>
+ protocol implementation. The main difference is that instead
+ of <code>listenUDP</code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorMulticast.listenMulticast.html" title="twisted.internet.interfaces.IReactorMulticast.listenMulticast">listenMulticast</a></code>
+ is called with the port number. The server calls <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IMulticastTransport.joinGroup.html" title="twisted.internet.interfaces.IMulticastTransport.joinGroup">joinGroup</a></code> to
+ join a multicast group. A <code class="python">DatagramProtocol</code>
+ that is listening with multicast and has joined a group can receive
+ multicast datagrams, but also unicast datagrams sent directly to its
+ address. The server in the example above sends such a unicast message in
+ reply to the multicast message it receives from the client.
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DatagramProtocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MulticastPingClient</span>(<span class="py-src-parameter">DatagramProtocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startProtocol</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-comment"># Join the multicast address, so we can receive replies:</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">joinGroup</span>(<span class="py-src-string">&quot;228.0.0.5&quot;</span>)
+ <span class="py-src-comment"># Send to 228.0.0.5:8005 - all listeners on the multicast address</span>
+ <span class="py-src-comment"># (including us) will receive this message.</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">'Client: Ping'</span>, (<span class="py-src-string">&quot;228.0.0.5&quot;</span>, <span class="py-src-number">8005</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">datagramReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">datagram</span>, <span class="py-src-parameter">address</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Datagram %s received from %s&quot;</span> % (<span class="py-src-variable">repr</span>(<span class="py-src-variable">datagram</span>), <span class="py-src-variable">repr</span>(<span class="py-src-variable">address</span>))
+
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenMulticast</span>(<span class="py-src-number">8005</span>, <span class="py-src-variable">MulticastPingClient</span>(), <span class="py-src-variable">listenMultiple</span>=<span class="py-src-variable">True</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/udp/MulticastClient.py"><span class="filename">listings/udp/MulticastClient.py</span></a></div></div>
+
+ <p>Note that a multicast socket will have a default TTL (time to live) of
+ 1. That is, datagrams won't traverse more than one router hop, unless a
+ higher TTL is set with
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IMulticastTransport.setTTL.html" title="twisted.internet.interfaces.IMulticastTransport.setTTL">setTTL</a></code>. Other
+ functionality provided by the multicast transport
+ includes <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IMulticastTransport.setOutgoingInterface.html" title="twisted.internet.interfaces.IMulticastTransport.setOutgoingInterface">setOutgoingInterface</a></code>
+ and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IMulticastTransport.setLoopbackMode.html" title="twisted.internet.interfaces.IMulticastTransport.setLoopbackMode">setLoopbackMode</a></code>
+ -- see <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IMulticastTransport.html" title="twisted.internet.interfaces.IMulticastTransport">IMulticastTransport</a></code> for more
+ information.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/howto/vision.html b/doc/core/howto/vision.html
new file mode 100644
index 0000000..cd4997d
--- /dev/null
+++ b/doc/core/howto/vision.html
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Vision For Twisted</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Vision For Twisted</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <p>Many other documents in this repository are dedicated to
+ defining what Twisted is. Here, I will attempt to explain not
+ what Twisted is, but what it should be, once I've met my goals
+ with it.</p>
+
+ <p>First, Twisted should be fun. It began as a game, it is
+ being used commercially in games, and it will be, I hope, an
+ interactive and entertaining experience for the end-user.</p>
+
+ <p>Twisted is a platform for developing internet applications.
+ While Python by itself is a very powerful language, there are
+ many facilities it lacks which other languages have spent great
+ attention to adding. It can do this now; Twisted is a good (if
+ somewhat idiosyncratic) pure-python framework or library,
+ depending on how you treat it, and it continues to improve.</p>
+
+ <p>As a platform, Twisted should be focused on integration.
+ Ideally, all functionality will be accessible through all
+ protocols. Failing that, all functionality should be
+ configurable through at least one protocol, with a seamless and
+ consistent user-interface. The next phase of development will
+ be focusing strongly on a configuration system which will unify
+ many disparate pieces of the current infrastructure, and allow
+ them to be tacked together by a non-programmer.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/img/TwistedLogo.bmp b/doc/core/img/TwistedLogo.bmp
new file mode 100644
index 0000000..940ede0
--- /dev/null
+++ b/doc/core/img/TwistedLogo.bmp
Binary files differ
diff --git a/doc/core/img/cred-login.dia b/doc/core/img/cred-login.dia
new file mode 100644
index 0000000..f9dfaa7
--- /dev/null
+++ b/doc/core/img/cred-login.dia
Binary files differ
diff --git a/doc/core/img/cred-login.png b/doc/core/img/cred-login.png
new file mode 100644
index 0000000..a27dff4
--- /dev/null
+++ b/doc/core/img/cred-login.png
Binary files differ
diff --git a/doc/core/img/deferred-attach.dia b/doc/core/img/deferred-attach.dia
new file mode 100644
index 0000000..9e42967
--- /dev/null
+++ b/doc/core/img/deferred-attach.dia
Binary files differ
diff --git a/doc/core/img/deferred-attach.png b/doc/core/img/deferred-attach.png
new file mode 100644
index 0000000..8050058
--- /dev/null
+++ b/doc/core/img/deferred-attach.png
Binary files differ
diff --git a/doc/core/img/deferred-process.dia b/doc/core/img/deferred-process.dia
new file mode 100644
index 0000000..37c5dd3
--- /dev/null
+++ b/doc/core/img/deferred-process.dia
Binary files differ
diff --git a/doc/core/img/deferred-process.png b/doc/core/img/deferred-process.png
new file mode 100644
index 0000000..d4047eb
--- /dev/null
+++ b/doc/core/img/deferred-process.png
Binary files differ
diff --git a/doc/core/img/deferred-states.svg b/doc/core/img/deferred-states.svg
new file mode 100644
index 0000000..cc8e8da
--- /dev/null
+++ b/doc/core/img/deferred-states.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 -1 952 869" width="952pt" height="869pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2010-02-21 23:40Z</dc:date><!-- Produced by OmniGraffle Professional 5.2.1 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledArrow_Marker" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="black"><g><path d="M 8 0 L 0 -3 L 0 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><rect fill="white" width="943" height="859"/><g><title>Layer 1</title><g><use xl:href="#id34_Graphic" filter="url(#Shadow)"/><use xl:href="#id5_Graphic" filter="url(#Shadow)"/><use xl:href="#id7_Graphic" filter="url(#Shadow)"/><use xl:href="#id27_Graphic" filter="url(#Shadow)"/><use xl:href="#id43_Graphic" filter="url(#Shadow)"/><use xl:href="#id44_Graphic" filter="url(#Shadow)"/><use xl:href="#id55_Graphic" filter="url(#Shadow)"/><use xl:href="#id79_Graphic" filter="url(#Shadow)"/></g><g id="id34_Graphic"><ellipse cx="239.63925" cy="106.633636" rx="51.00007" ry="20.000038" fill="white"/><ellipse cx="239.63925" cy="106.633636" rx="51.00007" ry="20.000038" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(203.83925 92.63363)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x="16.458202" y="11" textLength="21.339844">Unfi</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x="37.798046" y="11" textLength="17.34375">red</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x="1.1183586" y="25" textLength="69.36328">No Canceller</tspan></text></g><g id="id5_Graphic"><ellipse cx="165.63925" cy="584.63367" rx="57.500095" ry="26.000063" fill="white"/><ellipse cx="165.63925" cy="584.63367" rx="57.500095" ry="26.000063" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(124.639244 570.63367)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x="5.9785156" y="11" textLength="73.376953">Synchronous </tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x="23.993164" y="25" textLength="34.013672">Result</tspan></text></g><g id="id7_Graphic"><ellipse cx="608.9178" cy="528.56982" rx="69.00013" ry="31.957996" fill="white"/><ellipse cx="608.9178" cy="528.56982" rx="69.00013" ry="31.957996" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(558.71777 521.56976)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".17851639" y="11" textLength="100.04297">Synchronous Error</tspan></text></g><path d="M 240.43747 127.13095 C 243.32816 201.3608 260.02661 277.95673 249.11038 349.84274 C 238.68134 418.52057 203.04144 482.93884 178.32253 549.1927" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 251.42224 126.549065 C 288.27274 188.83333 307.86975 251.1824 361.98477 313.42053 C 414.2224 373.49948 498.65482 433.50983 568.65564 493.54248" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="219.78763" y="324.83887" width="62" height="24" fill="white"/><text transform="translate(224.78763 329.83887)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x=".32714844" y="11" textLength="51.345703">callback()</tspan></text><rect x="313.9862" y="278.44937" width="59" height="24" fill="white"/><text transform="translate(318.9862 283.44937)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x=".4970703" y="11" textLength="48.00586">errback()</tspan></text><line x1="277.42453" y1="120.438515" x2="517.71863" y2="208.23016" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="395.30939" y="161.18964" width="53" height="24" fill="white"/><text transform="translate(400.30939 166.18964)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".16015625" y="11" textLength="42.679688">cancel()</tspan></text><path d="M 159.00711 558.31494 C 144.4371 500.496 113.50804 416.50674 115.292725 384.84082 C 117.07741 353.1749 161.24283 339.42096 169.71629 368.30038 C 177.75905 395.71185 168.38503 485.11023 166.39159 548.2332" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="68.465515" y="328.755" width="138" height="52" fill="white"/><text transform="translate(73.465515 333.755)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="18.639648" y="11" textLength="90.720703">invoke user code</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".29101562" y="25" textLength="127.41797">added with addCallback</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="17.640625" y="39" textLength="92.71875">that returns value</tspan></text><path d="M 546.85925 514.01202 C 524.109 508.67526 503.64008 496.9103 478.60172 498.00012 C 453.56335 499.08994 423.941 513.03003 396.61398 520.5515 C 369.28696 528.073 344.87448 534.74982 314.6232 543.13367 C 287.24933 550.7201 255.07932 559.71246 224.62679 568.20135" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="354.86337" y="498.65527" width="103" height="38" fill="white"/><text transform="translate(359.86337 503.65527)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="5.8125" y="11" textLength="81.375">invoke callback</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".140625" y="25" textLength="92.71875">that returns value</tspan></text><path d="M 146.4648 609.59656 C 127.38432 634.43707 98.757896 685.20398 89.21766 684.1256 C 79.67742 683.04724 85.01394 617.64142 89.21766 603.1256 C 91.945145 593.70734 98.213043 594.7976 104.57533 596.1692" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="9.500664" y="631.5583" width="149" height="38" fill="white"/><text transform="translate(14.500664 636.5583)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="43.827148" y="11" textLength="51.345703">callback()</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".4736328" y="25" textLength="33.339844">(raise </tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="33.157227" y="25" textLength="105.36914">AlreadyCalledError)</tspan></text><g id="id27_Graphic"><ellipse cx="583.5811" cy="232.29474" rx="65.500122" ry="39.66116" fill="white"/><ellipse cx="583.5811" cy="232.29474" rx="65.500122" ry="39.66116" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(536.1811 204.29474)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="12.378517" y="11" textLength="73.376953">Synchronous </tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="34.066994" y="25" textLength="26.666016">Error</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="16.881447" y="39" textLength="64.371094">+ Suppress </tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="10.046486" y="53" textLength="74.70703">AlreadyCalled</tspan></text></g><path d="M 542.61957 263.74374 C 519.5946 281.42157 465.79904 277.82715 473.5378 316.78256 C 480.92667 353.97662 544.4154 429.981 583.654 489.20453" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="398.86932" y="278.41492" width="154" height="38" fill="white"/><text transform="translate(403.86932 283.41492)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="47.99707" y="11" textLength="48.00586">errback()</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".3046875" y="25" textLength="143.390625">(no-op, discard other error)</tspan></text><path d="M 614.46655 267.7644 C 635.61487 292.05173 676.88593 302.49948 677.9178 340.63364 C 678.88934 376.53894 644.1884 437.00916 624.19873 487.34406" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="610.0517" y="349.84274" width="127" height="38" fill="white"/><text transform="translate(615.0517 354.84274)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="32.827148" y="11" textLength="51.345703">callback()</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".4716797" y="25" textLength="116.05664">(no-op, discard value)</tspan></text><path d="M 865.07477 277.16257 C 837.2231 312.98267 794.7537 329.3515 781.5113 384.63364 C 768.84692 437.50256 782.9063 525.99194 785.3534 598.9937" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="796.4464" y="306.68784" width="53" height="24" fill="white"/><text transform="translate(801.4464 311.68784)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".16015625" y="11" textLength="42.679688">cancel()</tspan></text><g id="id43_Graphic"><ellipse cx="880.2593" cy="257.63364" rx="51.000122" ry="20.000027" fill="white"/><ellipse cx="880.2593" cy="257.63364" rx="51.000122" ry="20.000027" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(844.45935 243.63364)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="2.7912102" y="11" textLength="21.339844">Unfi</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="24.131054" y="11" textLength="48.01172">red With </tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="10.4552727" y="25" textLength="50.689453">Canceller</tspan></text></g><g id="id44_Graphic"><ellipse cx="652.27655" cy="35.647697" rx="51.000122" ry="20.000032" fill="white"/><ellipse cx="652.27655" cy="35.647697" rx="51.000122" ry="20.000032" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(616.47656 21.647697)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="10.789257" y="11" textLength="53.35547">Does Not </tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="22.798046" y="25" textLength="26.003906">Exist</tspan></text></g><path d="M 684.58026 51.48888 C 727.7895 72.677933 783.1651 84.063606 814.2209 115.062386 C 843.01117 143.799835 850.91644 189.40921 866.9766 228.42392" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 601.12073 37.995434 C 537.89252 40.89722 465.22925 37.506042 411.41708 46.701664 C 361.41751 55.245777 327.66455 74.66111 287.52023 89.79429" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="384.8317" y="33.75193" width="65" height="24" fill="white"/><text transform="translate(389.8317 38.75193)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x=".16015625" y="11" textLength="54.679688">Deferred()</tspan></text><rect x="734.8824" y="117.07918" width="182" height="24" fill="white"/><text transform="translate(739.8824 122.07918)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x=".13378906" y="11" textLength="171.73242">Deferred(canceller=myFunction)</tspan></text><path d="M 154.78345 558.62067 C 143.58713 531.7917 134.88046 484.20755 121.191124 478.12564 C 107.50179 472.04373 70.650887 508.01828 72.639236 522.1256 C 74.37477 534.43927 103.80231 545.9059 124.66333 557.63324" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="62.292725" y="475.72116" width="53" height="38.00003" fill="white"/><text transform="translate(67.292725 480.72116)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".16015625" y="11" textLength="42.679688">cancel()</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="2.1582031" y="25" textLength="38.683594">(no-op)</tspan></text><path d="M 675.53845 519.35144 C 691.08 517.20093 717.54523 522.37872 722.1678 512.8993 C 726.79034 503.4199 715.73834 464.34906 703.27655 462.4693 C 692.5027 460.84415 672.50806 481.53854 655.2522 495.60666" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="693.9308" y="470.06503" width="53" height="38" fill="white"/><text transform="translate(698.9308 475.06503)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".16015625" y="11" textLength="42.679688">cancel()</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="2.1582031" y="25" textLength="38.683594">(no-op)</tspan></text><g id="id55_Graphic"><ellipse cx="482.63913" cy="710.63367" rx="63.500114" ry="26.000063" fill="white"/><ellipse cx="482.63913" cy="710.63367" rx="63.500114" ry="26.000063" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(436.83914 696.63367)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="17.67207" y="11" textLength="11.326172">W</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="28.558788" y="11" textLength="48.703125">aiting on </tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".43964767" y="25" textLength="90.720703">another Deferred</tspan></text></g><path d="M 209.08551 602.09143 L 285.13278 632.64917 L 347.77994 657.3728 L 427.26218 688.7633" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="280.41714" y="637.63763" width="131" height="38" fill="white"/><text transform="translate(285.41714 642.63763)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="15.139648" y="11" textLength="90.720703">invoke user code</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".13671875" y="25" textLength="120.72656">that returns a Deferred</tspan></text><path d="M 568.87152 555.01324 C 540.79688 573.5515 498.9215 589.1156 484.6392 610.63367 C 472.25235 629.29596 480.6141 652.44653 482.73215 674.24408" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="434.83826" y="576.12885" width="131" height="38" fill="white"/><text transform="translate(439.83826 581.12885)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="15.139648" y="11" textLength="90.720703">invoke user code</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".13671875" y="25" textLength="120.72656">that returns a Deferred</tspan></text><path d="M 452.45026 733.90747 C 429.87482 751.31177 382.742 770.89917 384.7172 786.1256 C 386.4858 799.7595 427.62817 809.90436 455.25803 821.24493" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="388.5693" y="745.3466" width="53" height="24" fill="white"/><text transform="translate(393.5693 750.3466)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".16015625" y="11" textLength="42.679688">cancel()</tspan></text><path d="M 384.65698 785.1944 C 357.65372 793.17065 333.47244 811.30353 303.63913 809.1256 C 273.80582 806.9477 227.6994 805.1427 205.63925 772.1256 C 184.88138 741.0576 182.89703 673.3456 173.18976 620.71753" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="121.24072" y="744.52844" width="153" height="24" fill="white"/><text transform="translate(126.24072 749.52844)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".4638672" y="11" textLength="142.072266">sub-deferred gives a result</tspan></text><path d="M 384.7172 786.1256 C 521.94653 753.81525 753.54895 727.36633 796.4464 689.1848 C 837.35297 652.77533 706.68738 605.67615 649.87036 563.1833" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="704.6239" y="686.4107" width="155" height="24" fill="white"/><text transform="translate(709.6239 691.4107)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".130859375" y="11" textLength="144.73828">sub-deferred gives an error</tspan></text><g id="id79_Graphic"><circle cx="475.86932" cy="830.9652" r="12.3870745" fill="white"/><circle cx="475.86932" cy="830.9652" r="12.3870745" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g><path d="M 488.53668 828.58936 C 530.2334 820.76886 609.3293 820.9651 613.63934 805.1256 C 617.62073 790.4942 557.7912 762.1718 522.5708 739.1314" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="504.27307" y="794.09607" width="218" height="24" fill="white"/><text transform="translate(509.27307 799.09607)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".27148438" y="11" textLength="207.45703">sub-deferred waits on another deferred</tspan></text><path d="M 619.2704 198.54285 C 638.81757 180.05676 658.65106 138.87544 677.9178 143.07907 C 697.1845 147.2827 739.65155 209.5166 734.8824 223.76712 C 730.62415 236.491 688.4826 229.05707 659.19812 228.46265" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="690.22546" y="162.56232" width="53" height="38" fill="white"/><text transform="translate(695.22546 167.56232)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".16015625" y="11" textLength="42.679688">cancel()</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="2.1582031" y="25" textLength="38.683594">(no-op)</tspan></text><path d="M 835.5697 267.7302 C 680.54077 302.75528 478.18283 324.035 370.43637 372.81592 C 266.8172 419.72824 250.6402 492.10233 195.77448 553.20007" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="308.44598" y="377.05264" width="62" height="24" fill="white"/><text transform="translate(313.44598 382.05264)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".32714844" y="11" textLength="51.345703">callback()</tspan></text><path d="M 879.15765 278.12851 C 873.2046 388.87375 896.83124 565.581 861.2967 610.39746 C 827.4226 653.1197 739.76782 576.01385 675.2422 550.42566" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="806.2241" y="610.99994" width="59" height="24" fill="white"/><text transform="translate(811.2241 615.99994)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".4970703" y="11" textLength="48.00586">errback()</tspan></text><path d="M 776.9486 417.86966 C 686.0307 426.38492 592.25293 432.92642 504.1676 443.41797 C 419.45862 453.50739 339.96509 467.25562 258.17505 479.38318" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="327.14407" y="432.20013" width="138" height="52" fill="white"/><text transform="translate(332.14407 437.20013)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="42.660156" y="11" textLength="42.679688">cancel()</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".30859375" y="25" textLength="127.38281">(with canceller that calls</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="36.329102" y="39" textLength="55.341797">callback())</tspan></text><path d="M 223.63176 584.20825 C 255.7534 583.97266 266.52045 590.73486 320.00632 583.50134 C 371.20312 576.5774 461.56976 556.82605 534.85626 542.66675" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="294.61975" y="552.19177" width="118" height="52" fill="white"/><text transform="translate(299.61975 557.19177)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="8.6396484" y="11" textLength="90.720703">invoke user code</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".31054688" y="25" textLength="107.378906">that returns Failure /</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="9.9785156" y="39" textLength="88.04297">raises Exception</tspan></text><path d="M 603.0141 560.90674 C 597.15417 593.00348 578.55408 642.6284 585.4326 657.20667 C 592.31116 671.7849 637.78595 664.54333 644.28943 648.38507 C 650.11395 633.9137 634.90002 598.23065 626.91156 569.83502" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="556.41516" y="638.25708" width="118" height="52" fill="white"/><text transform="translate(561.41516 643.25708)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x="8.6396484" y="11" textLength="90.720703">invoke user code</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".31054688" y="25" textLength="107.378906">that returns Failure /</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="9.9785156" y="39" textLength="88.04297">raises Exception</tspan></text></g></g></svg>
diff --git a/doc/core/img/deferred.dia b/doc/core/img/deferred.dia
new file mode 100644
index 0000000..f27410a
--- /dev/null
+++ b/doc/core/img/deferred.dia
Binary files differ
diff --git a/doc/core/img/deferred.png b/doc/core/img/deferred.png
new file mode 100644
index 0000000..069d1d5
--- /dev/null
+++ b/doc/core/img/deferred.png
Binary files differ
diff --git a/doc/core/index.html b/doc/core/index.html
new file mode 100644
index 0000000..37b8b1d
--- /dev/null
+++ b/doc/core/index.html
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Core Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Core Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Core to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Core</li>
+<li><a href="specifications/index.html" shape="rect">Specifications</a>: specification
+documents for elements of Twisted Core</li>
+<li><a href="development/index.html" shape="rect">Development of Twisted</a>: for people who
+want to work on Twisted itself</li>
+</ul>
+<p>An <a href="http://twistedmatrix.com/documents/current/api/" shape="rect">API
+ reference</a> is available on the twistedmatrix web site.</p>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/man/manhole-man.html b/doc/core/man/manhole-man.html
new file mode 100644
index 0000000..68b6e64
--- /dev/null
+++ b/doc/core/man/manhole-man.html
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: MANHOLE.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">MANHOLE.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>manhole - Connect to a Twisted Manhole service
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>manhole</strong> </p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>manhole is a GTK interface to Twisted Manhole services. You can execute python code as if at an interactive Python console inside a running Twisted process with this.
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Chris Armstrong, copied from Moshe Zadka's <q>faucet</q> manpage.
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2000-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/man/manhole.1 b/doc/core/man/manhole.1
new file mode 100644
index 0000000..3d78617
--- /dev/null
+++ b/doc/core/man/manhole.1
@@ -0,0 +1,16 @@
+.TH MANHOLE "1" "August 2001" "" ""
+.SH NAME
+manhole \- Connect to a Twisted Manhole service
+.SH SYNOPSIS
+.B manhole
+.SH DESCRIPTION
+manhole is a GTK interface to Twisted Manhole services. You can execute python code as if at an interactive Python console inside a running Twisted process with this.
+.SH AUTHOR
+Written by Chris Armstrong, copied from Moshe Zadka's "faucet" manpage.
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/man/pyhtmlizer-man.html b/doc/core/man/pyhtmlizer-man.html
new file mode 100644
index 0000000..ad6a6b0
--- /dev/null
+++ b/doc/core/man/pyhtmlizer-man.html
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: pyhtmlizer.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">pyhtmlizer.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNTAX</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">OPTIONS</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>pyhtmlizer - pretty-print Python source as HTML
+
+</p>
+
+<h2>SYNTAX<a name="auto1"/></h2>
+
+<p>pyhtmlizer [<em>-s|--stylesheet</em> &lt;<em>url</em>&gt;] &lt;<em>filename</em>&gt;
+</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>This generates a HTML document with Python source marked up with span elements. To colorize, provide a stylesheet.
+</p>
+
+<h2>OPTIONS<a name="auto3"/></h2>
+
+<dl><dt><strong>--stylesheet, -s</strong> &lt;<em>url</em>&gt;
+</dt><dd>Links to the stylesheet at &lt;<em>url</em>&gt;.
+</dd>
+
+<dt><strong>--help</strong>
+</dt><dd>Output help information and exit.
+</dd>
+
+<dt><strong>-v</strong>, <strong>--version</strong>
+</dt><dd>Output version information and exit.
+</dd>
+
+</dl>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/man/pyhtmlizer.1 b/doc/core/man/pyhtmlizer.1
new file mode 100644
index 0000000..9621e60
--- /dev/null
+++ b/doc/core/man/pyhtmlizer.1
@@ -0,0 +1,22 @@
+.TH "pyhtmlizer" "1" "" "Twisted Matrix Laboratories" ""
+.SH "NAME"
+.LP
+pyhtmlizer \- pretty\-print Python source as HTML
+
+.SH "SYNTAX"
+.LP
+pyhtmlizer [\fI\-s|\-\-stylesheet\fR <\fIurl\fR>] <\fIfilename\fR>
+.SH "DESCRIPTION"
+.LP
+This generates a HTML document with Python source marked up with span elements. To colorize, provide a stylesheet.
+.SH "OPTIONS"
+.LP
+.TP
+\fB\-\-stylesheet, \-s\fR <\fIurl\fR>
+Links to the stylesheet at <\fIurl\fR>.
+.TP
+\fB\-\-help\fR
+Output help information and exit.
+.TP
+\fB\-v\fR, \fB\--version\fR
+Output version information and exit.
diff --git a/doc/core/man/tap2deb-man.html b/doc/core/man/tap2deb-man.html
new file mode 100644
index 0000000..d42b2a0
--- /dev/null
+++ b/doc/core/man/tap2deb-man.html
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: TAP2DEB.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">TAP2DEB.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>tap2deb - create Debian packages which wrap .tap files
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>tap2deb</strong> [options]
+</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>Create a ready to upload Debian package in <q>.build</q>
+<dl><dt><strong>-u</strong>, <strong>--unsigned</strong>
+</dt><dd>do not sign the Debian package
+</dd>
+
+<dt><strong>-t</strong>, <strong>--tapfile</strong> <em>&lt;tapfile&gt;</em>
+</dt><dd>Build the application around the given .tap (default twistd.tap)
+</dd>
+
+<dt><strong>-y</strong>, <strong>--type</strong> <em>&lt;type&gt;</em>
+</dt><dd>The configuration has the given type . Allowable types are
+<strong>tap</strong>, <strong>source</strong>, <strong>xml</strong> and <strong>python</strong>.
+The first three types are <strong>mktap</strong> output formats,
+while the last one is a manual building of application
+(see <strong>twistd(1)</strong>, the <strong>-y</strong> option).
+</dd>
+
+<dt><strong>-p</strong>, <strong>--protocol</strong> <em>&lt;protocol&gt;</em>
+</dt><dd>The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+</dd>
+
+<dt><strong>-d</strong>, <strong>--debfile</strong> <em>&lt;debfile&gt;</em>
+</dt><dd>The name of the debian package. Default is 'twisted-'+protocol.
+</dd>
+
+<dt><strong>-V</strong>, <strong>--set-version</strong> <em>&lt;version&gt;</em>
+</dt><dd>The version of the Debian package. The default is 1.0
+</dd>
+
+<dt><strong>-e</strong>, <strong>--description</strong> <em>&lt;description&gt;</em>
+</dt><dd>The one-line description. Default is uninteresting.
+</dd>
+
+<dt><strong>-l</strong>, <strong>--long_description</strong> <em>&lt;long_description&gt;</em>
+</dt><dd>A multi-line description. Default is explanation about
+this being an automatic package created from tap2deb.
+</dd>
+
+<dt><strong>-m</strong>, <strong>--maintainer</strong> <em>&lt;maintainer&gt;</em>
+</dt><dd>The maintainer, as <q>Name Lastname &lt;email address&gt;</q>. This will
+go in the meta-files, as well as be used as the id to sign the package.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Output version information and exit.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Moshe Zadka, based on twistd's help messages
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2000-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/man/tap2deb.1 b/doc/core/man/tap2deb.1
new file mode 100644
index 0000000..de52a70
--- /dev/null
+++ b/doc/core/man/tap2deb.1
@@ -0,0 +1,55 @@
+.TH TAP2DEB "1" "July 2001" "" ""
+.SH NAME
+tap2deb \- create Debian packages which wrap .tap files
+.SH SYNOPSIS
+.B tap2deb
+[options]
+.SH DESCRIPTION
+Create a ready to upload Debian package in ".build"
+.TP
+\fB\-u\fR, \fB\--unsigned\fR
+do not sign the Debian package
+.TP
+\fB\-t\fR, \fB\--tapfile\fR \fI<tapfile>\fR
+Build the application around the given .tap (default twistd.tap)
+.TP
+\fB\-y\fR, \fB\--type\fR \fI<type>\fR
+The configuration has the given type . Allowable types are
+\fBtap\fR, \fBsource\fR, \fBxml\fR and \fBpython\fR.
+The first three types are \fBmktap\fR output formats,
+while the last one is a manual building of application
+(see \fBtwistd(1)\fR, the \fB\-y\fR option).
+.TP
+\fB\-p\fR, \fB\--protocol\fR \fI<protocol>\fR
+The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+.TP
+\fB\-d\fR, \fB\--debfile\fR \fI<debfile>\fR
+The name of the debian package. Default is 'twisted-'+protocol.
+.TP
+\fB\-V\fR, \fB\--set-version\fR \fI<version>\fR
+The version of the Debian package. The default is 1.0
+.TP
+\fB\-e\fR, \fB\--description\fR \fI<description>\fR
+The one-line description. Default is uninteresting.
+.TP
+\fB\-l\fR, \fB\--long_description\fR \fI<long_description>\fR
+A multi-line description. Default is explanation about
+this being an automatic package created from tap2deb.
+.TP
+\fB\-m\fR, \fB\--maintainer\fR \fI<maintainer>\fR
+The maintainer, as "Name Lastname <email address>". This will
+go in the meta-files, as well as be used as the id to sign the package.
+.TP
+\fB\--version\fR
+Output version information and exit.
+.SH AUTHOR
+Written by Moshe Zadka, based on twistd's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/man/tap2rpm-man.html b/doc/core/man/tap2rpm-man.html
new file mode 100644
index 0000000..8d4535f
--- /dev/null
+++ b/doc/core/man/tap2rpm-man.html
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: TAP2RPM.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">TAP2RPM.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>tap2rpm - create RPM packages which wrap .tap files
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>tap2rpm</strong> [options]
+</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>Create a set of RPM/SRPM packages in the current directory
+<dl><dt><strong>-t</strong>, <strong>--tapfile</strong> <em>&lt;tapfile&gt;</em>
+</dt><dd>Build the application around the given .tap (default twistd.tap)
+</dd>
+
+<dt><strong>-y</strong>, <strong>--type</strong> <em>&lt;type&gt;</em>
+</dt><dd>The configuration has the given type . Allowable types are
+<strong>tap</strong>, <strong>source</strong>, <strong>xml</strong> and <strong>python</strong>.
+The first three types are <strong>mktap</strong> output formats,
+while the last one is a manual building of application
+(see <strong>twistd(1)</strong>, the <strong>-y</strong> option).
+</dd>
+
+<dt><strong>-p</strong>, <strong>--protocol</strong> <em>&lt;protocol&gt;</em>
+</dt><dd>The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+</dd>
+
+<dt><strong>-d</strong>, <strong>--rpmfile</strong> <em>&lt;rpmfile&gt;</em>
+</dt><dd>The name of the RPM package. Default is 'twisted-'+protocol.
+</dd>
+
+<dt><strong>-V</strong>, <strong>--set-version</strong> <em>&lt;version&gt;</em>
+</dt><dd>The version of the RPM package. The default is 1.0
+</dd>
+
+<dt><strong>-e</strong>, <strong>--description</strong> <em>&lt;description&gt;</em>
+</dt><dd>The one-line description. Default is uninteresting.
+</dd>
+
+<dt><strong>-l</strong>, <strong>--long_description</strong> <em>&lt;long_description&gt;</em>
+</dt><dd>A multi-line description. Default is explanation about
+this being an automatic package created from tap2rpm.
+</dd>
+
+<dt><strong>-m</strong>, <strong>--maintainer</strong> <em>&lt;maintainer&gt;</em>
+</dt><dd>The maintainer, as <q>Name Lastname &lt;email address&gt;</q>. This will
+go in the meta-files.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Output version information and exit.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>tap2rpm was written by Sean Reifschneider based on tap2deb by Moshe Zadka.
+This man page is heavily based on the tap2deb man page by Moshe Zadka.
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit
+<em>http://twistedmatrix.com/trac/wiki/TwistedDevelopment#FilingTickets</em> for more
+information.
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2000-2009 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/man/tap2rpm.1 b/doc/core/man/tap2rpm.1
new file mode 100644
index 0000000..6d53c9d
--- /dev/null
+++ b/doc/core/man/tap2rpm.1
@@ -0,0 +1,55 @@
+.TH TAP2RPM "1" "July 2001" "" ""
+.SH NAME
+tap2rpm \- create RPM packages which wrap .tap files
+.SH SYNOPSIS
+.B tap2rpm
+[options]
+.SH DESCRIPTION
+Create a set of RPM/SRPM packages in the current directory
+.TP
+\fB\-t\fR, \fB\--tapfile\fR \fI<tapfile>\fR
+Build the application around the given .tap (default twistd.tap)
+.TP
+\fB\-y\fR, \fB\--type\fR \fI<type>\fR
+The configuration has the given type . Allowable types are
+\fBtap\fR, \fBsource\fR, \fBxml\fR and \fBpython\fR.
+The first three types are \fBmktap\fR output formats,
+while the last one is a manual building of application
+(see \fBtwistd(1)\fR, the \fB\-y\fR option).
+.TP
+\fB\-p\fR, \fB\--protocol\fR \fI<protocol>\fR
+The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+.TP
+\fB\-d\fR, \fB\--rpmfile\fR \fI<rpmfile>\fR
+The name of the RPM package. Default is 'twisted-'+protocol.
+.TP
+\fB\-V\fR, \fB\--set-version\fR \fI<version>\fR
+The version of the RPM package. The default is 1.0
+.TP
+\fB\-e\fR, \fB\--description\fR \fI<description>\fR
+The one-line description. Default is uninteresting.
+.TP
+\fB\-l\fR, \fB\--long_description\fR \fI<long_description>\fR
+A multi-line description. Default is explanation about
+this being an automatic package created from tap2rpm.
+.TP
+\fB\-m\fR, \fB\--maintainer\fR \fI<maintainer>\fR
+The maintainer, as "Name Lastname <email address>". This will
+go in the meta-files.
+.TP
+\fB\--version\fR
+Output version information and exit.
+.SH AUTHOR
+tap2rpm was written by Sean Reifschneider based on tap2deb by Moshe Zadka.
+This man page is heavily based on the tap2deb man page by Moshe Zadka.
+.SH "REPORTING BUGS"
+To report a bug, visit
+\fIhttp://twistedmatrix.com/trac/wiki/TwistedDevelopment#FilingTickets\fR for more
+information.
+.SH COPYRIGHT
+Copyright \(co 2000-2009 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/man/tapconvert-man.html b/doc/core/man/tapconvert-man.html
new file mode 100644
index 0000000..20c979b
--- /dev/null
+++ b/doc/core/man/tapconvert-man.html
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: TAPCONVERT.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">TAPCONVERT.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>tapconvert - convert Twisted configurations from one format to another
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>tapconvert</strong> -i <em>input</em> -o <em>output</em> [-f <em>input-type</em>] [-t <em>output-type</em>] [-d] [-e]</p>
+
+<p><strong>tapconvert</strong> --help</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>The <strong>--help</strong> prints out a usage message to standard output.
+<dl><dt><strong>--in</strong>, <strong>-i</strong> <em>&lt;input file&gt;</em>
+</dt><dd>The name of the input configuration.
+</dd>
+
+<dt><strong>--out</strong>, <strong>-o</strong> <em>&lt;output file&gt;</em>
+</dt><dd>The name of the output configuration.
+</dd>
+
+<dt><strong>--typein</strong>, <strong>-f</strong> <em>&lt;input type&gt;</em>
+</dt><dd>The type of the input file. Can be either 'guess', 'python', 'pickle', 'xml', or 'source'. Default is 'guess'.
+</dd>
+
+<dt><strong>--typeout</strong>, <strong>-t</strong> <em>&lt;output type&gt;</em>
+</dt><dd>The type of the output file. Can be either 'pickle', 'xml', or 'source'. Default is 'source'.
+</dd>
+
+<dt><strong>--decrypt</strong>, <strong>-d</strong>
+</dt><dd>Decrypt the specified tap/aos/xml input file.
+</dd>
+
+<dt><strong>--encrypt</strong>, <strong>-e</strong>
+</dt><dd>Encrypt output file before writing.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Output version information and exit.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Moshe Zadka, based on tapconvert's help messages
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2000-2012 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/man/tapconvert.1 b/doc/core/man/tapconvert.1
new file mode 100644
index 0000000..d37bfac
--- /dev/null
+++ b/doc/core/man/tapconvert.1
@@ -0,0 +1,40 @@
+.TH TAPCONVERT "1" "July 2001" "" ""
+.SH NAME
+tapconvert \- convert Twisted configurations from one format to another
+.SH SYNOPSIS
+.B tapconvert -i \fIinput\fR -o \fIoutput\fR [-f \fIinput-type\fR] [-t \fIoutput-type\fR] [-d] [-e]
+.PP
+.B tapconvert --help
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB\--in\fR, \fB\-i\fR \fI<input file>\fR
+The name of the input configuration.
+.TP
+\fB\--out\fR, \fB\-o\fR \fI<output file>\fR
+The name of the output configuration.
+.TP
+\fB\--typein\fR, \fB\-f\fR \fI<input type>\fR
+The type of the input file. Can be either 'guess', 'python', 'pickle', 'xml', or 'source'. Default is 'guess'.
+.TP
+\fB\--typeout\fR, \fB\-t\fR \fI<output type>\fR
+The type of the output file. Can be either 'pickle', 'xml', or 'source'. Default is 'source'.
+.TP
+\fB\--decrypt\fR, \fB\-d\fR
+Decrypt the specified tap/aos/xml input file.
+.TP
+\fB\--encrypt\fR, \fB\-e\fR
+Encrypt output file before writing.
+.TP
+\fB\--version\fR
+Output version information and exit.
+.SH AUTHOR
+Written by Moshe Zadka, based on tapconvert's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2012 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/man/trial-man.html b/doc/core/man/trial-man.html
new file mode 100644
index 0000000..7707c6b
--- /dev/null
+++ b/doc/core/man/trial-man.html
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: TRIAL.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">TRIAL.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">OPTIONS</a></li><li><a href="#auto4">SEE ALSO</a></li><li><a href="#auto5">AUTHOR</a></li><li><a href="#auto6">REPORTING BUGS</a></li><li><a href="#auto7">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>trial - run unit tests
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>trial</strong> [ <em>options</em> ] [ <em>file</em> | <em>package</em> | <em>module</em> | <em>TestCase</em> | <em>testmethod</em> ] ...
+</p>
+
+<p><strong>trial --help</strong> | <strong>-h</strong>
+</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>trial loads and executes a suite of unit tests, obtained from modules,
+packages and files listed on the command line.
+</p>
+
+<p>trial will take either filenames or fully qualified Python names as
+arguments. Thus `trial myproject/foo.py', `trial myproject.foo' and
+`trial myproject.foo.SomeTestCase.test_method' are all valid ways to
+invoke trial.
+</p>
+
+<p>After running the given test suite, the default test reporter prints a summary
+of the test run. This consists of the word <q>PASSED</q> (if all tests ran as
+expected) or <q>FAILED</q> (if any test behaved unexpectedly) followed by a count of
+the different kinds of test results encountered. The possible kinds of test
+results includes:
+<dl><dt>successes
+</dt><dd>Tests that passed all their assertions and completed without error.
+These are marked <q>PASSED</q> in the normal test output.
+</dd>
+
+<dt>failures
+</dt><dd>Tests that failed an assertion, called self.fail() or explicitly raised
+self.failureException for some reason. These are marked <q>FAILED</q> in the
+normal test output.
+</dd>
+
+<dt>errors
+</dt><dd>Tests that raised an unexpected exception (including AssertionError),
+tests that caused the tearDown() method to raise an exception, tests
+that run for longer than the timeout interval, tests that caused
+something to call twisted.python.log.err() without subsequently calling
+self.flushLoggedErrors(), tests that leave the reactor in an unclean
+state, etc. These are marked <q>ERROR</q> in the normal test output.
+Note that because errors can be caused after the actual test method
+returns, it is possible for a single test to be reported as both an
+error and a failure, and hence the total number of test results can be
+greater than the total number of tests executed.
+</dd>
+
+<dt>skips
+</dt><dd>Tests that were skipped, usually because of missing dependencies. These
+are marked <q>SKIPPED</q> in the normal test output.
+</dd>
+
+<dt>expectedFailures
+</dt><dd>Tests that failed, but were expected to fail, usually because the test
+is for a feature that hasn't been implemented yet. These are marked
+<q>TODO</q> in the normal test output.
+</dd>
+
+<dt>unexpectedSuccesses
+</dt><dd>Tests that should have been listed under expectedFailures, except that
+for some reason the test succeeded. These are marked <q>SUCCESS!?!</q> in
+the normal test output.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>OPTIONS<a name="auto3"/></h2>
+
+<dl><dt><strong>-b</strong>, <strong>--debug</strong>
+</dt><dd>Run the tests in the Python debugger. Also does post-mortem
+debugging on exceptions. Will load `.pdbrc' from current directory if
+it exists.
+</dd>
+
+<dt><strong>-B</strong>, <strong>--debug-stacktraces</strong>
+</dt><dd>Report Deferred creation and callback stack traces
+</dd>
+
+<dt><strong>--coverage</strong>
+</dt><dd>Generate coverage information in the `coverage' subdirectory of the trial temp
+directory (`_trial_temp' by default). For each Python module touched by the
+execution of the given tests, a file will be created in the coverage directory
+named for the module's fully-qualified name with the suffix `.cover'. For
+example, because the trial test runner is written in Python, the coverage
+directory will almost always contain a file named `twisted.trial.runner.cover'.
+
+Each `.cover' file contains a copy of the Python source of the module in
+question, with a prefix at the beginning of each line containing coverage
+information. For lines that are not executable (blank lines, comments, etc.)
+the prefix is blank. For executable lines that were run in the course of the
+test suite, the prefix is a number indicating the number of times that line was
+executed. The string `&gt;&gt;&gt;&gt;&gt;&gt;' prefixes executable lines that were not executed
+in the course of the test suite.
+
+Note that this functionality uses Python's sys.settrace() function, so tests
+that call sys.settrace() themselves are likely to break trial's coverage
+functionality.
+</dd>
+
+<dt><strong>--disablegc</strong>
+</dt><dd>Disable the garbage collector for the duration of the test run. As each test is
+run, trial saves the TestResult objects, which means that Python's garbage
+collector has more non-garbage objects to wade through, making each
+garbage-collection run slightly slower. Disabling garbage collection entirely
+will make some test suites complete faster (contrast --force-gc, below), at the
+cost of increasing (possibly greatly) memory consumption. This option also makes
+tests slightly more deterministic, which might help debugging in extreme
+circumstances.
+</dd>
+
+<dt><strong>-e</strong>, <strong>--rterrors</strong>
+</dt><dd>Print tracebacks to standard output as soon as they occur
+</dd>
+
+<dt><strong>--force-gc</strong>
+</dt><dd>Run gc.collect() before and after each test case. This can be used to
+isolate errors that occur when objects get collected. This option would be
+the default, except it makes tests run about ten times slower.
+</dd>
+
+<dt><strong>-h</strong>, <strong>--help</strong>
+</dt><dd>Print a usage message to standard output, then exit.
+</dd>
+
+<dt><strong>--help-reporters</strong>
+</dt><dd>Print a list of valid reporters to standard output, then exit. Reporters can
+be selected with the --reporter option described below.
+</dd>
+
+<dt><strong>--help-reactors</strong>
+</dt><dd>Print a list of possible reactors to standard output, then exit. Not all listed
+reactors are available on every platform. Reactors can be selected with the
+--reactor option described below.
+</dd>
+
+<dt><strong>-l</strong>, <strong>--logfile</strong> <em>logfile</em>
+</dt><dd>Direct the log to a different file. The default file is `test.log'.
+<em>logfile</em> is relative to _trial_temp.
+</dd>
+
+<dt><strong>-n</strong>, <strong>--dry-run</strong>
+</dt><dd>Go through all the tests and make them pass without running.
+</dd>
+
+<dt><strong>-N</strong>, <strong>--no-recurse</strong>
+</dt><dd>By default, trial recurses through packages to find every module inside
+every subpackage. Unless, that is, you specify this option.
+</dd>
+
+<dt><strong>--nopm</strong>
+</dt><dd>Don't automatically jump into debugger for post-mortem analysis of
+exceptions. Only usable in conjunction with --debug.
+</dd>
+
+<dt><strong>--profile</strong>
+</dt><dd>Run tests under the Python profiler.
+</dd>
+
+<dt><strong>-r</strong>, <strong>--reactor</strong> <em>reactor</em>
+</dt><dd>Choose which reactor to use. See --help-reactors for a list.
+</dd>
+
+<dt><strong>--recursionlimit</strong>
+</dt><dd>Set Python's recursion limit. See sys.setrecursionlimit()
+</dd>
+
+<dt><strong>--reporter</strong>
+</dt><dd>Select the reporter to use for trial's output. Use the --help-reporters
+option to see a list of valid reporters.
+</dd>
+
+<dt><strong>--spew</strong>
+</dt><dd>Print an insanely verbose log of everything that happens. Useful when
+debugging freezes or locks in complex code.
+</dd>
+
+<dt><strong>--tbformat</strong> <em>format</em>
+</dt><dd>Format to display tracebacks with. Acceptable values are `default', `brief'
+and `verbose'. `brief' produces tracebacks that play nicely with Emacs' GUD.
+</dd>
+
+<dt><strong>--temp-directory</strong> <em>directory</em>
+</dt><dd>WARNING: Do not use this options unless you know what you are doing.
+By default, trial creates a directory called _trial_temp under the current
+working directory. When trial runs, it first <em>deletes</em> this directory,
+then creates it, then changes into the directory to run the tests. The log
+file and any coverage files are stored here. Use this option if you wish to
+have trial run in a directory other than _trial_temp. Be warned, trial
+will <em>delete</em> the directory before re-creating it.
+</dd>
+
+<dt><strong>--testmodule</strong> <em>filename</em>
+</dt><dd>Ask trial to look into <em>filename</em> and run any tests specified using the
+Emacs-style buffer variable `test-case-name'.
+</dd>
+
+<dt><strong>--unclean-warnings</strong>
+</dt><dd>As of Twisted 8.0, trial will report an error if the reactor is left unclean
+at the end of the test. This option is provided to assist in migrating from
+Twisted 2.5 to Twisted 8.0 and later. Enabling this option will turn the errors
+into warnings.
+</dd>
+
+<dt><strong>-u</strong>, <strong>--until-failure</strong>
+</dt><dd>Keep looping the tests until one of them raises an error or a failure.
+This is particularly useful for reproducing intermittent failures.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Prints the Twisted version number and exit.
+</dd>
+
+<dt><strong>--without-module</strong> <em>modulenames</em>
+</dt><dd>Simulate the lack of the specified comma-separated list of modules. This makes
+it look like the modules are not present in the system, causing tests to check
+the behavior for that configuration.
+</dd>
+
+<dt><strong>-z</strong>, <strong>--random</strong> [<em>seed</em>]
+</dt><dd>Run the tests in random order using the specified seed.
+</dd>
+
+</dl>
+
+<h2>SEE ALSO<a name="auto4"/></h2>
+
+<p>The latest version of the trial documentation can be found at
+http://twistedmatrix.com/documents/current/core/howto/testing.html
+</p>
+
+<h2>AUTHOR<a name="auto5"/></h2>
+
+<p>Written by Jonathan M. Lange
+</p>
+
+<h2>REPORTING BUGS<a name="auto6"/></h2>
+
+<p>To report a bug, visit http://twistedmatrix.com/trac/newticket
+</p>
+
+<h2>COPYRIGHT<a name="auto7"/></h2>
+
+<p>Copyright © 2003-2011 Twisted Matrix Laboratories
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/man/trial.1 b/doc/core/man/trial.1
new file mode 100644
index 0000000..f01934d
--- /dev/null
+++ b/doc/core/man/trial.1
@@ -0,0 +1,200 @@
+.TH TRIAL "1" "Oct 2007" "" ""
+.SH NAME
+trial \- run unit tests
+.SH SYNOPSIS
+\fBtrial\fR [ \fIoptions\fR ] [ \fIfile\fR | \fIpackage\fR | \fImodule\fR | \fITestCase\fR | \fItestmethod\fR ] ...
+.PP
+\fBtrial --help\fR | \fB-h\fR
+.SH DESCRIPTION
+.PP
+trial loads and executes a suite of unit tests, obtained from modules,
+packages and files listed on the command line.
+.PP
+trial will take either filenames or fully qualified Python names as
+arguments. Thus `trial myproject/foo.py', `trial myproject.foo' and
+`trial myproject.foo.SomeTestCase.test_method' are all valid ways to
+invoke trial.
+.PP
+After running the given test suite, the default test reporter prints a summary
+of the test run. This consists of the word "PASSED" (if all tests ran as
+expected) or "FAILED" (if any test behaved unexpectedly) followed by a count of
+the different kinds of test results encountered. The possible kinds of test
+results includes:
+.TP
+successes
+Tests that passed all their assertions and completed without error.
+These are marked "PASSED" in the normal test output.
+.TP
+failures
+Tests that failed an assertion, called self.fail() or explicitly raised
+self.failureException for some reason. These are marked "FAILED" in the
+normal test output.
+.TP
+errors
+Tests that raised an unexpected exception (including AssertionError),
+tests that caused the tearDown() method to raise an exception, tests
+that run for longer than the timeout interval, tests that caused
+something to call twisted.python.log.err() without subsequently calling
+self.flushLoggedErrors(), tests that leave the reactor in an unclean
+state, etc. These are marked "ERROR" in the normal test output.
+.IP
+Note that because errors can be caused after the actual test method
+returns, it is possible for a single test to be reported as both an
+error and a failure, and hence the total number of test results can be
+greater than the total number of tests executed.
+.TP
+skips
+Tests that were skipped, usually because of missing dependencies. These
+are marked "SKIPPED" in the normal test output.
+.TP
+expectedFailures
+Tests that failed, but were expected to fail, usually because the test
+is for a feature that hasn't been implemented yet. These are marked
+"TODO" in the normal test output.
+.TP
+unexpectedSuccesses
+Tests that should have been listed under expectedFailures, except that
+for some reason the test succeeded. These are marked "SUCCESS!?!" in
+the normal test output.
+.SH OPTIONS
+.TP
+\fB-b\fR, \fB--debug\fR
+Run the tests in the Python debugger. Also does post-mortem
+debugging on exceptions. Will load `.pdbrc' from current directory if
+it exists.
+.TP
+\fB-B\fR, \fB--debug-stacktraces\fR
+Report Deferred creation and callback stack traces
+.TP
+\fB--coverage\fR
+Generate coverage information in the `coverage' subdirectory of the trial temp
+directory (`_trial_temp' by default). For each Python module touched by the
+execution of the given tests, a file will be created in the coverage directory
+named for the module's fully-qualified name with the suffix `.cover'. For
+example, because the trial test runner is written in Python, the coverage
+directory will almost always contain a file named `twisted.trial.runner.cover'.
+
+Each `.cover' file contains a copy of the Python source of the module in
+question, with a prefix at the beginning of each line containing coverage
+information. For lines that are not executable (blank lines, comments, etc.)
+the prefix is blank. For executable lines that were run in the course of the
+test suite, the prefix is a number indicating the number of times that line was
+executed. The string `>>>>>>' prefixes executable lines that were not executed
+in the course of the test suite.
+
+Note that this functionality uses Python's sys.settrace() function, so tests
+that call sys.settrace() themselves are likely to break trial's coverage
+functionality.
+.TP
+\fB--disablegc\fR
+Disable the garbage collector for the duration of the test run. As each test is
+run, trial saves the TestResult objects, which means that Python's garbage
+collector has more non-garbage objects to wade through, making each
+garbage-collection run slightly slower. Disabling garbage collection entirely
+will make some test suites complete faster (contrast --force-gc, below), at the
+cost of increasing (possibly greatly) memory consumption. This option also makes
+tests slightly more deterministic, which might help debugging in extreme
+circumstances.
+.TP
+\fB-e\fR, \fB--rterrors\fR
+Print tracebacks to standard output as soon as they occur
+.TP
+\fB--force-gc\fR
+Run gc.collect() before and after each test case. This can be used to
+isolate errors that occur when objects get collected. This option would be
+the default, except it makes tests run about ten times slower.
+.TP
+\fB-h\fR, \fB--help\fR
+Print a usage message to standard output, then exit.
+.TP
+\fB--help-reporters\fR
+Print a list of valid reporters to standard output, then exit. Reporters can
+be selected with the --reporter option described below.
+.TP
+\fB--help-reactors\fR
+Print a list of possible reactors to standard output, then exit. Not all listed
+reactors are available on every platform. Reactors can be selected with the
+--reactor option described below.
+.TP
+\fB-l\fR, \fB--logfile\fR \fIlogfile\fR
+Direct the log to a different file. The default file is `test.log'.
+\fIlogfile\fR is relative to _trial_temp.
+.TP
+\fB-n\fR, \fB--dry-run\fR
+Go through all the tests and make them pass without running.
+.TP
+\fB-N\fR, \fB--no-recurse\fR
+By default, trial recurses through packages to find every module inside
+every subpackage. Unless, that is, you specify this option.
+.TP
+\fB--nopm\fR
+Don't automatically jump into debugger for post-mortem analysis of
+exceptions. Only usable in conjunction with --debug.
+.TP
+\fB--profile\fR
+Run tests under the Python profiler.
+.TP
+\fB-r\fR, \fB--reactor\fR \fIreactor\fR
+Choose which reactor to use. See --help-reactors for a list.
+.TP
+\fB--recursionlimit\fR
+Set Python's recursion limit. See sys.setrecursionlimit()
+.TP
+\fB--reporter\fR
+Select the reporter to use for trial's output. Use the --help-reporters
+option to see a list of valid reporters.
+.TP
+\fB--spew\fR
+Print an insanely verbose log of everything that happens. Useful when
+debugging freezes or locks in complex code.
+.TP
+\fB--tbformat\fR \fIformat\fR
+Format to display tracebacks with. Acceptable values are `default', `brief'
+and `verbose'. `brief' produces tracebacks that play nicely with Emacs' GUD.
+.TP
+\fB--temp-directory\fR \fIdirectory\fR
+WARNING: Do not use this options unless you know what you are doing.
+By default, trial creates a directory called _trial_temp under the current
+working directory. When trial runs, it first \fIdeletes\fR this directory,
+then creates it, then changes into the directory to run the tests. The log
+file and any coverage files are stored here. Use this option if you wish to
+have trial run in a directory other than _trial_temp. Be warned, trial
+will \fIdelete\fR the directory before re-creating it.
+.TP
+\fB--testmodule\fR \fIfilename\fR
+Ask trial to look into \fIfilename\fR and run any tests specified using the
+Emacs-style buffer variable `test-case-name'.
+.TP
+\fB--unclean-warnings\fR
+As of Twisted 8.0, trial will report an error if the reactor is left unclean
+at the end of the test. This option is provided to assist in migrating from
+Twisted 2.5 to Twisted 8.0 and later. Enabling this option will turn the errors
+into warnings.
+.TP
+\fB-u\fR, \fB--until-failure\fR
+Keep looping the tests until one of them raises an error or a failure.
+This is particularly useful for reproducing intermittent failures.
+.TP
+\fB--version\fR
+Prints the Twisted version number and exit.
+.TP
+\fB--without-module\fR \fImodulenames\fR
+Simulate the lack of the specified comma-separated list of modules. This makes
+it look like the modules are not present in the system, causing tests to check
+the behavior for that configuration.
+.TP
+\fB-z\fR, \fB--random\fR [\fIseed\fR]
+Run the tests in random order using the specified seed.
+.PP
+.SH SEE ALSO
+The latest version of the trial documentation can be found at
+http://twistedmatrix.com/documents/current/core/howto/testing.html
+.SH AUTHOR
+Written by Jonathan M. Lange
+.SH "REPORTING BUGS"
+To report a bug, visit http://twistedmatrix.com/trac/newticket
+.SH COPYRIGHT
+Copyright \(co 2003-2011 Twisted Matrix Laboratories
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/man/twistd-man.html b/doc/core/man/twistd-man.html
new file mode 100644
index 0000000..7761034
--- /dev/null
+++ b/doc/core/man/twistd-man.html
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: TWISTD.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">TWISTD.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">OPTIONS</a></li><li><a href="#auto4">SIGNALS</a></li><li><a href="#auto5">AUTHOR</a></li><li><a href="#auto6">REPORTING BUGS</a></li><li><a href="#auto7">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>twistd - run Twisted applications (TACs, TAPs)
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>twistd</strong> [options]
+</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>Read a twisted.application.service.Application out of a file and run it.
+</p>
+
+<h2>OPTIONS<a name="auto3"/></h2>
+
+<p><strong>-n</strong>, <strong>--nodaemon</strong>
+Don't daemonize (stay in foreground).
+<dl><dt><strong>-q</strong>, <strong>--quiet</strong>
+</dt><dd>No-op for backwards compatibility.
+</dd>
+
+<dt><strong>-p</strong>, <strong>--profile</strong> <em>&lt;profile output&gt;</em>
+</dt><dd>Run the application under the profiler, dumping results to the specified file.
+</dd>
+
+<dt><strong>--profiler</strong> <em>&lt;profiler name&gt;</em>
+</dt><dd>Specify the profiler to use. Defaults to the 'hotshot' profiler.
+</dd>
+
+<dt><strong>--savestats</strong>
+</dt><dd>Save the Stats object rather than the text output of the profiler.
+</dd>
+
+<dt><strong>-b</strong>, <strong>--debug</strong>
+</dt><dd>Run the application in the Python Debugger (implies <strong>--nodaemon</strong> option).
+Sending a SIGINT or SIGUSR2 signal to the process will drop it into the
+debugger.
+</dd>
+
+<dt><strong>-e</strong>, <strong>--encrypted</strong> <em>&lt;file&gt;</em>
+</dt><dd>The specified tap/aos file is encrypted.
+</dd>
+
+<dt><strong>--euid</strong>
+</dt><dd>Set only effective user-id rather than real user-id. This option has no
+effect unless the server is running as root, in which case it means not
+to shed all privileges after binding ports, retaining the option to regain
+privileges in cases such as spawning processes. Use with caution.
+</dd>
+
+<dt><strong>-o</strong>, <strong>--no_save</strong>
+</dt><dd>Do not save shutdown state.
+</dd>
+
+<dt><strong>--originalname</strong>
+</dt><dd>Behave as though the specified Application has no process name set, and run
+with the standard process name (the Python binary in most cases).
+</dd>
+
+<dt><strong>-l</strong>, <strong>--logfile</strong> <em>&lt;logfile&gt;</em>
+</dt><dd>Log to a specified file, - for stdout (default: twistd.log).
+The log file will be rotated on SIGUSR1.
+</dd>
+
+<dt><strong>-l</strong>, <strong>--logger</strong> <em>&lt;fully qualified python name&gt;</em>
+</dt><dd>A fully-qualified name to a log observer factory to use for the initial log
+observer. Takes precedence over --logfile and --syslog.
+</dd>
+
+<dt><strong>--pidfile</strong> <em>&lt;pidfile&gt;</em>
+</dt><dd>Save pid in specified file (default: twistd.pid).
+</dd>
+
+<dt><strong>--chroot</strong> <em>&lt;directory&gt;</em>
+</dt><dd>Chroot to a supplied directory before running (default: don't chroot).
+Chrooting is done before changing the current directory.
+</dd>
+
+<dt><strong>-d</strong>, <strong>--rundir</strong> <em>&lt;directory&gt;</em>
+</dt><dd>Change to a supplied directory before running (default: .).
+</dd>
+
+<dt><strong>-u</strong>, <strong>--uid</strong> <em>&lt;uid&gt;</em>
+</dt><dd>The uid to run as (default: don't change).
+</dd>
+
+<dt><strong>-g</strong>, <strong>--gid</strong> <em>&lt;gid&gt;</em>
+</dt><dd>The gid to run as (default: don't change).
+</dd>
+
+<dt><strong>--umask</strong> <em>&lt;mask&gt;</em>
+</dt><dd>The (octal) file creation mask to apply. (default: 0077 for daemons, no
+change otherwise).
+</dd>
+
+<dt><strong>-r</strong>, <strong>--reactor</strong> <em>&lt;reactor&gt;</em>
+</dt><dd>Choose which reactor to use. See <strong>--help-reactors</strong> for a list of
+possibilities.
+</dd>
+
+<dt><strong>--help-reactors</strong>
+</dt><dd>List the names of possibly available reactors.
+</dd>
+
+<dt><strong>--spew</strong>
+</dt><dd>Write an extremely verbose log of everything that happens. Useful for
+debugging freezes or locks in complex code.
+</dd>
+
+<dt><strong>-f</strong>, <strong>--file</strong> <em>&lt;tap file&gt;</em>
+</dt><dd>Read the given .tap file (default: twistd.tap).
+</dd>
+
+<dt><strong>-s</strong>, <strong>--source</strong> <em>&lt;tas file&gt;</em>
+</dt><dd>Load an Application from the given .tas (AOT Python source) file.
+</dd>
+
+<dt><strong>-y</strong>, <strong>--python</strong> <em>&lt;python file&gt;</em>
+</dt><dd>Use the variable <q>application</q> from the given Python file. This option overrides
+<strong>-f</strong>. This option implies <strong>--no_save</strong>.
+</dd>
+
+<dt><strong>--syslog</strong>
+</dt><dd>Log to syslog instead of a file.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Print version information and exit.
+</dd>
+
+<dt><strong>--prefix</strong> <em>&lt;prefix&gt;</em>
+</dt><dd>Use the specified prefix when logging to logfile. Default is <q>twisted</q>.
+</dd>
+
+</dl>
+
+</p>
+
+<p>Note that if <strong>twistd</strong> is run as root, the working directory is <em>not</em>
+searched for Python modules.
+</p>
+
+<h2>SIGNALS<a name="auto4"/></h2>
+
+<p>A running twistd accepts SIGINT for a clean shutdown and SIGUSR1 to rotate log
+files.
+</p>
+
+<h2>AUTHOR<a name="auto5"/></h2>
+
+<p>Written by Moshe Zadka, based on twistd's help messages.
+</p>
+
+<h2>REPORTING BUGS<a name="auto6"/></h2>
+
+<p>To report a bug, visit
+<em>http://twistedmatrix.com/trac/wiki/TwistedDevelopment#DevelopmentProcess</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto7"/></h2>
+
+<p>Copyright © 2001-2011 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/man/twistd.1 b/doc/core/man/twistd.1
new file mode 100644
index 0000000..c5c4c3c
--- /dev/null
+++ b/doc/core/man/twistd.1
@@ -0,0 +1,118 @@
+.TH TWISTD "1" "Dec 2011" "" ""
+.SH NAME
+twistd \- run Twisted applications (TACs, TAPs)
+.SH SYNOPSIS
+.B twistd
+[options]
+.SH DESCRIPTION
+Read a twisted.application.service.Application out of a file and run it.
+.SH OPTIONS
+\fB\-n\fR, \fB\--nodaemon\fR
+Don't daemonize (stay in foreground).
+.TP
+\fB\-q\fR, \fB\--quiet\fR
+No-op for backwards compatibility.
+.TP
+\fB\-p\fR, \fB\--profile\fR \fI<profile output>\fR
+Run the application under the profiler, dumping results to the specified file.
+.TP
+\fB\--profiler\fR \fI<profiler name>\fR
+Specify the profiler to use. Defaults to the 'hotshot' profiler.
+.TP
+\fB--savestats\fR
+Save the Stats object rather than the text output of the profiler.
+.TP
+\fB\-b\fR, \fB\--debug\fR
+Run the application in the Python Debugger (implies \fB\--nodaemon\fR option).
+Sending a SIGINT or SIGUSR2 signal to the process will drop it into the
+debugger.
+.TP
+\fB\-e\fR, \fB\--encrypted\fR \fI<file>\fR
+The specified tap/aos file is encrypted.
+.TP
+\fB--euid\fR
+Set only effective user-id rather than real user-id. This option has no
+effect unless the server is running as root, in which case it means not
+to shed all privileges after binding ports, retaining the option to regain
+privileges in cases such as spawning processes. Use with caution.
+.TP
+\fB\-o\fR, \fB\--no_save\fR
+Do not save shutdown state.
+.TP
+\fB\--originalname\fR
+Behave as though the specified Application has no process name set, and run
+with the standard process name (the Python binary in most cases).
+.TP
+\fB\-l\fR, \fB\--logfile\fR \fI<logfile>\fR
+Log to a specified file, - for stdout (default: twistd.log).
+The log file will be rotated on SIGUSR1.
+.TP
+\fB\-l\fR, \fB\--logger\fR \fI<fully qualified python name>\fR
+A fully-qualified name to a log observer factory to use for the initial log
+observer. Takes precedence over --logfile and --syslog.
+.TP
+\fB\--pidfile\fR \fI<pidfile>\fR
+Save pid in specified file (default: twistd.pid).
+.TP
+\fB\--chroot\fR \fI<directory>\fR
+Chroot to a supplied directory before running (default: don't chroot).
+Chrooting is done before changing the current directory.
+.TP
+\fB\-d\fR, \fB\--rundir\fR \fI<directory>\fR
+Change to a supplied directory before running (default: .).
+.TP
+\fB\-u\fR, \fB\--uid\fR \fI<uid>\fR
+The uid to run as (default: don't change).
+.TP
+\fB\-g\fR, \fB\--gid\fR \fI<gid>\fR
+The gid to run as (default: don't change).
+.TP
+\fB--umask\fR \fI<mask>\fR
+The (octal) file creation mask to apply. (default: 0077 for daemons, no
+change otherwise).
+.TP
+\fB\-r\fR, \fB\--reactor\fR \fI<reactor>\fR
+Choose which reactor to use. See \fB\--help-reactors\fR for a list of
+possibilities.
+.TP
+\fB--help-reactors\fR
+List the names of possibly available reactors.
+.TP
+\fB\--spew\fR
+Write an extremely verbose log of everything that happens. Useful for
+debugging freezes or locks in complex code.
+.TP
+\fB\-f\fR, \fB\--file\fR \fI<tap file>\fR
+Read the given .tap file (default: twistd.tap).
+.TP
+\fB\-s\fR, \fB\--source\fR \fI<tas file>\fR
+Load an Application from the given .tas (AOT Python source) file.
+.TP
+\fB\-y\fR, \fB\--python\fR \fI<python file>\fR
+Use the variable "application" from the given Python file. This option overrides
+\fB\-f\fR. This option implies \fB\--no_save\fR.
+.TP
+\fB\--syslog\fR
+Log to syslog instead of a file.
+.TP
+\fB\--version\fR
+Print version information and exit.
+.TP
+\fB\--prefix\fR \fI<prefix>\fR
+Use the specified prefix when logging to logfile. Default is "twisted".
+.PP
+Note that if \fBtwistd\fR is run as root, the working directory is \fInot\fR
+searched for Python modules.
+.SH SIGNALS
+A running twistd accepts SIGINT for a clean shutdown and SIGUSR1 to rotate log
+files.
+.SH AUTHOR
+Written by Moshe Zadka, based on twistd's help messages.
+.SH "REPORTING BUGS"
+To report a bug, visit
+\fIhttp://twistedmatrix.com/trac/wiki/TwistedDevelopment#DevelopmentProcess\fR
+.SH COPYRIGHT
+Copyright \(co 2001-2011 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/specifications/banana.html b/doc/core/specifications/banana.html
new file mode 100644
index 0000000..53405de
--- /dev/null
+++ b/doc/core/specifications/banana.html
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Banana Protocol Specifications</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Banana Protocol Specifications</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Banana Encodings</a></li><li><a href="#auto2">Element Types</a></li><ul><li><a href="#auto3">Examples</a></li></ul><li><a href="#auto4">Profiles</a></li><ul><li><a href="#auto5">The &quot;none&quot; Profile</a></li><li><a href="#auto6">The &quot;pb&quot; Profile</a></li></ul><li><a href="#auto7">Protocol Handshake and Behaviour</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Introduction<a name="auto0"/></h2>
+
+ <p>
+ Banana is an efficient, extendable protocol for sending and receiving s-expressions.
+ A s-expression in this context is a list composed of byte strings, integers,
+ large integers, floats and/or s-expressions.
+ </p>
+
+ <h2>Banana Encodings<a name="auto1"/></h2>
+
+ <p>
+ The banana protocol is a stream of data composed of elements. Each element has the
+ following general structure - first, the length of element encoded in base-128, least signficant
+ bit first. For example length 4674 will be sent as <code>0x42 0x24</code>. For certain element
+ types the length will be omitted (e.g. float) or have a different meaning (it is the actual
+ value of integer elements).
+ </p>
+
+ <p>
+ Following the length is a delimiter byte, which tells us what kind of element this
+ is. Depending on the element type, there will then follow the number of bytes specified
+ in the length. The byte's high-bit will always be set, so that we can differentiate
+ between it and the length (since the length bytes use 128-base, their high bit will
+ never be set).
+ </p>
+
+ <h2>Element Types<a name="auto2"/></h2>
+
+ <p>
+ Given a series of bytes that gave us length N, these are the different delimiter bytes:
+ </p>
+
+ <dl>
+ <dt>List -- 0x80</dt>
+
+ <dd>The following bytes are a list of N elements. Lists may be nested,
+ and a child list counts as only one element to its parent (regardless
+ of how many elements the child list contains). </dd>
+
+ <dt>Integer -- 0x81</dt>
+ <dd>The value of this element is the positive integer N. Following bytes are not part of this element. Integers can have values of 0 &lt;= N &lt;= 2147483647.</dd>
+
+ <dt>String -- 0x82</dt>
+ <dd>The following N bytes are a string element.</dd>
+
+ <dt>Negative Integer -- 0x83</dt>
+ <dd>The value of this element is the integer N * -1, i.e. -N. Following bytes are not part of this element. Negative integers can have values of 0 &gt;= -N &gt;= -2147483648.</dd>
+
+ <dt>Float - 0x84</dt>
+ <dd>The next 8 bytes are the float encoded in IEEE 754 floating-point <q>double format</q> bit layout.
+ No length bytes should have been defined.
+ </dd>
+
+ <dt>Large Integer -- 0x85</dt>
+ <dd>The value of this element is the positive large integer N. Following bytes are not part of this element. Large integers have no size limitation.</dd>
+
+ <dt>Large Negative Integer -- 0x86</dt>
+ <dd>The value of this element is the negative large integer -N. Following bytes are not part of this element. Large integers have no size limitation.</dd>
+ </dl>
+
+ <p>
+ Large integers are intended for arbitary length integers. Regular integers types (positive and negative) are limited to 32-bit values.
+ </p>
+
+ <h3>Examples<a name="auto3"/></h3>
+
+ <p>
+ Here are some examples of elements and their encodings - the type bytes are marked in bold:
+ </p>
+
+ <dl>
+ <dt><code>1</code></dt>
+ <dd><code>0x01 <strong>0x81</strong></code></dd>
+ <dt><code>-1</code></dt>
+ <dd><code>0x01 <strong>0x83</strong></code></dd>
+ <dt><code>1.5</code></dt>
+ <dd><code><strong>0x84</strong> 0x3f 0xf8 0x00 0x00 0x00 0x00 0x00 0x00</code></dd>
+ <dt><code>&quot;hello&quot;</code></dt>
+ <dd><code>0x05 <strong>0x82</strong> 0x68 0x65 0x6c 0x6c 0x6f</code></dd>
+ <dt><code>[]</code></dt>
+ <dd><code>0x00 <strong>0x80</strong></code></dd>
+ <dt><code>[1, 23]</code></dt>
+ <dd><code>0x02 <strong>0x80</strong> 0x01 <strong>0x81</strong> 0x17 <strong>0x81</strong></code></dd>
+ <dt><code>123456789123456789</code></dt>
+ <dd><code>0x15 0x3e 0x41 0x66 0x3a 0x69 0x26 0x5b 0x01 <strong>0x85</strong></code></dd>
+ <dt><code>[1, [&quot;hello&quot;]]</code></dt>
+ <dd><code>0x02 <strong>0x80</strong> 0x01 <strong>0x81</strong> 0x01 <strong>0x80</strong> 0x05 <strong>0x82</strong> 0x68 0x65 0x6c 0x6c 0x6f</code></dd>
+ </dl>
+
+ <h2>Profiles<a name="auto4"/></h2>
+
+ <p>
+ The Banana protocol is extendable. Therefore, it supports the concept of profiles. Profiles allow
+ developers to extend the banana protocol, adding new element types, while still keeping backwards
+ compatability with implementations that don't support the extensions. The profile used in each
+ session is determined at the handshake stage (see below.)
+ </p>
+
+ <p>
+ A profile is specified by a unique string. This specification defines two profiles
+ - <code>&quot;none&quot;</code> and <code>&quot;pb&quot;</code>. The <code>&quot;none&quot;</code> profile is the standard
+ profile that should be supported by all Banana implementations.
+ Additional profiles may be added in the future.
+ </p>
+
+ <h3>The <code>&quot;none&quot;</code> Profile<a name="auto5"/></h3>
+
+ <p>
+ The <code>&quot;none&quot;</code> profile is identical to the delimiter types listed above. It is highly recommended
+ that all Banana clients and servers support the <code>&quot;none&quot;</code> profile.
+ </p>
+
+ <h3>The <code>&quot;pb&quot;</code> Profile<a name="auto6"/></h3>
+
+ <p>
+ The <code>&quot;pb&quot;</code> profile is intended for use with the Perspective Broker protocol, that runs on top
+ of Banana. Basically, it converts commonly used PB strings into shorter versions, thus
+ minimizing bandwidth usage. It starts with a single byte, which tells us to which string element
+ to convert it, and ends with the delimiter byte, <code>0x87</code>, which should not be prefixed
+ by a length.
+ </p>
+
+ <dl>
+ <dt>0x01</dt> <dd>'None'</dd>
+ <dt>0x02</dt> <dd>'class'</dd>
+ <dt>0x03</dt> <dd>'dereference'</dd>
+ <dt>0x04</dt> <dd>'reference'</dd>
+ <dt>0x05</dt> <dd>'dictionary'</dd>
+ <dt>0x06</dt> <dd>'function'</dd>
+ <dt>0x07</dt> <dd>'instance'</dd>
+ <dt>0x08</dt> <dd>'list'</dd>
+ <dt>0x09</dt> <dd>'module'</dd>
+ <dt>0x0a</dt> <dd>'persistent'</dd>
+ <dt>0x0b</dt> <dd>'tuple'</dd>
+ <dt>0x0c</dt> <dd>'unpersistable'</dd>
+ <dt>0x0d</dt> <dd>'copy'</dd>
+ <dt>0x0e</dt> <dd>'cache'</dd>
+ <dt>0x0f</dt> <dd>'cached'</dd>
+ <dt>0x10</dt> <dd>'remote'</dd>
+ <dt>0x11</dt> <dd>'local'</dd>
+ <dt>0x12</dt> <dd>'lcache'</dd>
+ <dt>0x13</dt> <dd>'version'</dd>
+ <dt>0x14</dt> <dd>'login'</dd>
+ <dt>0x15</dt> <dd>'password'</dd>
+ <dt>0x16</dt> <dd>'challenge'</dd>
+ <dt>0x17</dt> <dd>'logged_in'</dd>
+ <dt>0x18</dt> <dd>'not_logged_in'</dd>
+ <dt>0x19</dt> <dd>'cachemessage'</dd>
+ <dt>0x1a</dt> <dd>'message'</dd>
+ <dt>0x1b</dt> <dd>'answer'</dd>
+ <dt>0x1c</dt> <dd>'error'</dd>
+ <dt>0x1d</dt> <dd>'decref'</dd>
+ <dt>0x1e</dt> <dd>'decache'</dd>
+ <dt>0x1f</dt> <dd>'uncache'</dd>
+ </dl>
+
+ <h2>Protocol Handshake and Behaviour<a name="auto7"/></h2>
+
+ <p>
+ The initiating side of the connection will be referred to as <q>client</q>, and the other
+ side as <q>server</q>.
+ </p>
+
+ <p>
+ Upon connection, the server will send the client a list of string elements, signifying
+ the profiles it supports. It is recommended that <code>&quot;none&quot;</code> be included in this list. The client
+ then sends the server a string from this list, telling the server which profile it wants to
+ use. At this point the whole session will use this profile.
+ </p>
+
+ <p>
+ Once a profile has been established, the two sides may start exchanging elements. There is no
+ limitation on order or dependencies of messages. Any such limitation (e.g. <q>server can only
+ send an element to client in response to a request from client</q>) is application specific.
+ </p>
+
+ <p>
+ Upon receiving illegal messages, failed handshakes, etc., a Banana client or server should
+ close its connection.
+ </p>
+
+ </div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/core/specifications/index.html b/doc/core/specifications/index.html
new file mode 100644
index 0000000..ac19236
--- /dev/null
+++ b/doc/core/specifications/index.html
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Specifications</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Specifications</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="banana.html" shape="rect">Banana</a></li>
+</ul>
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/fun/Twisted.Quotes b/doc/fun/Twisted.Quotes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/fun/Twisted.Quotes
diff --git a/doc/fun/lightbulb b/doc/fun/lightbulb
new file mode 100644
index 0000000..12de989
--- /dev/null
+++ b/doc/fun/lightbulb
@@ -0,0 +1,7 @@
+Q. How many Twisted developers does it take to screw in a lightbulb?
+
+A. Three to implement twisted.lightbulb, one to refactor it, four to whine until the API is documented, two to re-implement it as a C module, one to package it up nicely, but nobody uses the packaged lightbulb, because they need the newest light features, and then no one actually gets around to screwing it in.
+%
+Q. How many Divmod developers does it take to reboot a server?
+A. Four. One to drive, one to recompile the kernel and one to report the progress on IRC.
+%
diff --git a/doc/fun/register.html b/doc/fun/register.html
new file mode 100644
index 0000000..3fe220b
--- /dev/null
+++ b/doc/fun/register.html
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Twisted Matrix Labs Software Registration</title>
+ </head>
+
+ <body>
+ <h1>How to Register your Twisted Daemon instance</h1>
+
+ <h2>The Problem</h2>
+
+ <p>The Business Software Alliance may have put it best, in
+ their page on <a
+ href="http://www.bsa.org/usa/antipiracy/">anti-piracy</a>:</p>
+
+ <blockquote>
+ "Software is one of the most valuable technologies of the
+ Information Age, running everything from PCs to the Internet.
+ Unfortunately, because software is so valuable, and because
+ computers make it easy to create an exact copy of a program
+ in seconds, software piracy is widespread. From individual
+ computer users to professionals who deal wholesale in stolen
+ software, piracy exists in homes, schools, businesses and
+ government. Software pirates not only steal from the
+ companies that make the software, but with less money for
+ research and development of new software, all users are hurt.
+ That's why all software piracy - even one copy you make for a
+ friend, is illegal."
+ </blockquote>
+
+ <p>Software piracy is a serious crime, and one that the Open
+ Source community has been remarkably lax in pursuing and
+ protecting against. This is why Twisted Matrix Laboratories is
+ taking the forefront in Open Source software registration
+ technology.</p>
+
+ <h2>The Twisted Solution</h2>
+
+ <p>In order to do your part to prevent the tragedy of
+ unregistered, unlicensed software, all you need to do is visit
+ <a href="http://www.twistedmatrix.com/license">the Twisted
+ Matrix Labs Licensing and Registration page</a>, and enter your
+ user information to obtain a license key. You can provide us
+ with as much or as little information as you like!</p>
+
+ <p>This will produce a plain-text file which you should save as
+ "twisted-registration" (no quotes, no extension) and drop into
+ the same folder where you run your <code>twistd</code> server.
+ It will be automatically recognized by the server upon
+ startup.</p>
+
+ <h2>Other Benefits to You, the User</h2>
+
+ <p>Besides providing you with a license for the use of your
+ Twisted Daemon, a Twisted registration file also provides you
+ with a probably-unique identifier for your Twisted process -- a
+ helpful tool in writing peer-to-peer applications, or assigning
+ distributed database keys. <b>Privacy is important to us, and
+ this ID is <i>never</i> used by Twisted Matrix Labs software
+ for tracking purposes.</b> You need only register once, rather
+ than renegotiating unique IDs upon every run of your peered
+ application.</p>
+
+ <p>Registering with a valid e-mail address also gives Twisted
+ Matrix Labs a way to contact you with information about
+ upgrades and services as they become available. (Again,
+ registering with an e-mail address is <em>strictly
+ optional</em> to obtain your license key. If you don't want to
+ receive this information, you don't have to!)</p>
+
+ <p>Thank you for doing your part to end the piracy crisis.</p>
+ </body>
+</html>
+
diff --git a/doc/historic/2002/ipc10/twisted-network-framework/errata.html b/doc/historic/2002/ipc10/twisted-network-framework/errata.html
new file mode 100644
index 0000000..8388919
--- /dev/null
+++ b/doc/historic/2002/ipc10/twisted-network-framework/errata.html
@@ -0,0 +1,256 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>The World of Software is a World of Constant
+ Change</title>
+ </head>
+
+ <body>
+ <p><em><strong>Note:</strong> This document is relevant for the
+ version of Twisted that was current at <a
+ href="http://www.python10.com">IPC10</a>. It has since been
+ superseded by many changes to the Python API. It is remaining
+ unchanged for historical reasons, but please refer to
+ documentation for the specific system you are looking for and
+ not these papers for current information.</em></p>
+
+ <h1>The World of Software is a World of Constant Change</h1>
+
+ <p>Twisted has undergone several major revisions since Moshe
+ Zadka and I wrote the <a href="ipc10paper.html">"The Twisted
+ Network Framework"</a>. Most of these changes have not deviated
+ from the central vision of the framework, but almost all of the
+ code listings have been re-visited and enhanced in some
+ way.</p>
+
+ <p>So, while the paper was correct at the time that it was
+ originally written, a few things have changed which have
+ invalidated portions of it.</p>
+
+ <p>Most significant is the fact that almost all methods which
+ pass callbacks of some kind have been changed to take no
+ callback or error-callback arguments, and instead return an
+ instance of a <code
+ class="API">twisted.python.defer.Deferred</code>. This means
+ that an asynchronous function can be easily identified visually
+ because it will be of the form: <code
+ class="python">async_obj.asyncMethod("foo")<b>.addCallbacks(succeded,
+ failed)</b></code>. There is also a utility method <code
+ class="python">addCallback</code> which makes it more
+ convenient to pass additional arguments to a callback function
+ and omit special-case error handling.</p>
+
+ <p>While it is still backwards compatible, <code
+ class="API">twisted.internet.passport</code> has been re-named
+ to <code class="API">twisted.cred</code>, and the various
+ classes in it have been split out into submodules of that
+ package, and the various remote-object superclasses have been
+ moved out of twisted.spread.pb and put into
+ twisted.spread.flavors.</p>
+
+ <p><code class="python">Application.listenOn</code> has been
+ replaced with the more descripively named <code
+ class="python">Application.listenTCP</code>, <code
+ class="python">Application.listenUDP</code>, and <code
+ class="python">Application.listenSSL</code>.</p>
+
+ <p><code class="API">twisted.web.widgets</code> has progressed
+ quite far since the paper was written! One description
+ specifically given in the paper is no longer correct:</p>
+
+ <blockquote>
+ The namespace for evaluating the template expressions is
+ obtained by scanning the class hierarchy for attributes, and
+ getting each of those attributes from the current instance.
+ This means that all methods will be bound methods, so
+ indicating "self" explicitly is not required. While it is
+ possible to override the method for creating namespaces,
+ using this default has the effect of associating all
+ presentation code for a particular widget in one class, along
+ with its template. If one is working with a non-programmer
+ designer, and the template is in an external file, it is
+ always very clear to the designer what functionality is
+ available to them in any given scope, because there is a list
+ of available methods for any given class.
+ </blockquote>
+<p>This is still possible to avoid breakages in old code, but
+after some experimentation, it became clear that simply passing
+ <code class="python">self</code> was an easier method for
+ creating the namespace, both for designers and programmers.</p>
+ <p>In addition, since the advent of Zope3, interoperability
+ with Zope has become increasingly interesting possibility for
+ the Twisted development team, since it would be desirable if
+ Twisted could use their excellent strategy for
+ content-management, while still maintaining Twisted's
+ advantages in the arena of multi-protocol servers. Of
+ particular interest has been Zope Presentation Templates, since
+ they seem to be a truly robust solution for keeping design
+ discrete from code, compatible with the event-based method in
+ which twisted.web.widgets processes web requests. <code
+ class="API">twisted.web.widgets.ZopePresentationTemplate</code>
+ may be opening soon in a theatre near you!</p>
+
+ <p>The following code examples are corrected or modernized
+ versions of the ones that appear in the paper.</p>
+
+ <blockquote>
+ Listing 9: A remotely accessible object and accompanying call
+
+<pre class="python">
+# Server Side
+class MyObject(pb.Referenceable):
+ def remote_doIt(self):
+ return "did it"
+
+# Client Side
+ ...
+ def myCallback(result):
+ print result # result will be 'did it'
+ def myErrback(stacktrace):
+ print 'oh no, mr. bill!'
+ print stacktrace
+ myRemoteReference.doIt().addCallbacks(myCallback,
+ myErrback)
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 10: An object responding to its calling perspective
+<pre class="python">
+# Server Side
+class Greeter(pb.Viewable):
+ def view_greet(self, actor):
+ return "Hello %s!\n" % actor.perspectiveName
+
+# Client Side
+ ...
+ remoteGreeter.greet().addCallback(sys.stdout.write)
+ ...
+</pre>
+ </blockquote>
+
+
+ <blockquote>
+ Listing 12: A client for Echoer objects.
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def gotObject(object):
+ print "got object:",object
+ object.echo("hello network".addCallback(gotEcho)
+def gotEcho(echo):
+ print 'server echoed:',echo
+ main.shutDown()
+def gotNoObject(reason):
+ print "no object:",reason
+ main.shutDown()
+pb.getObjectAt("localhost", 8789, gotObject, gotNoObject, 30)
+main.run()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 13: A PB server using twisted's "passport"
+ authentication.
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class SimplePerspective(pb.Perspective):
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+class SimpleService(pb.Service):
+ def getPerspectiveNamed(self, name):
+ return SimplePerspective(name, self)
+if __name__ == '__main__':
+ import pbecho
+ app = main.Application("pbecho")
+ pbecho.SimpleService("pbecho",app).getPerspectiveNamed("guest").makeIdentity("guest")
+ app.listenTCP(pb.portno, pb.BrokerFactory(pb.AuthRoot(app)))
+ app.save("start")
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 14: Connecting to an Authorized Service
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def success(message):
+ print "Message received:",message
+ main.shutDown()
+def failure(error):
+ print "Failure...",error
+ main.shutDown()
+def connected(perspective):
+ perspective.echo("hello world").addCallbacks(success, failure)
+ print "connected."
+
+pb.connect("localhost", pb.portno, "guest", "guest",
+ "pbecho", "guest", 30).addCallbacks(connected,
+ failure)
+main.run()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 15: A Twisted GUI application
+<pre class="python">
+from twisted.internet import main, ingtkernet
+from twisted.spread.ui import gtkutil
+import gtk
+ingtkernet.install()
+class EchoClient:
+ def __init__(self, echoer):
+ l.hide()
+ self.echoer = echoer
+ w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.GtkVBox(); b = gtk.GtkButton("Echo:")
+ self.entry = gtk.GtkEntry(); self.outry = gtk.GtkEntry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', gtk.mainquit)
+ w.show_all()
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.echo(txt).addCallback(self.outry.set_text)
+l = gtkutil.Login(EchoClient, None, initialService="pbecho")
+l.show_all()
+gtk.mainloop()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 16: an event-based web widget.
+<pre class="python">
+from twisted.spread import pb
+from twisted.python import defer
+from twisted.web import widgets
+class EchoDisplay(widgets.Gadget, widgets.Presentation):
+ template = """&lt;H1&gt;Welcome to my widget, displaying %%%%echotext%%%%.&lt;/h1&gt;
+ &lt;p&gt;Here it is: %%%%getEchoPerspective()%%%%&lt;/p&gt;"""
+ echotext = 'hello web!'
+ def getEchoPerspective(self):
+ return ['&lt;b&gt;',
+ pb.connect("localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 1).
+ addCallbacks(self.makeListOf, self.formatTraceback)
+ ,'&lt;/b&gt;']
+ def makeListOf(self, echoer):
+ return [echoer.echo(self.echotext).addCallback(lambda x: [x])]
+if __name__ == "__main__":
+ from twisted.web import server
+ from twisted.internet import main
+ a = main.Application("pbweb")
+ a.listenTCP(8080, server.Site(EchoDisplay()))
+ a.run()
+</pre>
+ </blockquote>
+ </body>
+</html>
+
diff --git a/doc/historic/2002/ipc10/twisted-network-framework/index.html b/doc/historic/2002/ipc10/twisted-network-framework/index.html
new file mode 100644
index 0000000..88fb488
--- /dev/null
+++ b/doc/historic/2002/ipc10/twisted-network-framework/index.html
@@ -0,0 +1,1568 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>The Twisted Network Framework</title>
+ </head>
+
+ <body>
+ <p><em><strong>Note:</strong> This document is relevant for the
+ version of Twisted that were current previous to <a
+ href="http://www.python10.com">IPC10</a>. Even at the time of
+ its release, <a href="ipc10errata.html">there were errata
+ issued</a> to make it current. It is remaining unaltered for
+ historical purposes but it is no longer accurate.</em></p>
+
+ <h1>The Twisted Network Framework</h1>
+
+ <h6>Moshe Zadka <a
+ href="mailto:m@moshez.org">m@moshez.org</a></h6>
+
+ <h6>Glyph Lefkowitz <a
+ href="mailto:glyph@twistedmatrix.com">glyph@twistedmatrix.com</a></h6>
+
+ <h3>Abstract</h3>
+
+ <p>Twisted is a framework for writing asynchronous,
+ event-driven networked programs in Python -- both clients and
+ servers. In addition to abstractions for low-level system calls
+ like <code>select(2)</code> and <code>socket(2)</code>, it also
+ includes a large number of utility functions and classes, which
+ make writing new servers easy. Twisted includes support for
+ popular network protocols like HTTP and SMTP, support for GUI
+ frameworks like <code>GTK+</code>/<code>GNOME</code> and
+ <code>Tk</code> and many other classes designed to make network
+ programs easy. Whenever possible, Twisted uses Python's
+ introspection facilities to save the client programmer as much
+ work as possible. Even though Twisted is still work in
+ progress, it is already usable for production systems -- it can
+ be used to bring up a Web server, a mail server or an IRC
+ server in a matter of minutes, and require almost no
+ configuration.</p>
+
+ <p><strong>Keywords:</strong> internet, network, framework,
+ event-based, asynchronous</p>
+
+ <h3>Introduction</h3>
+
+ <p>Python lends itself to writing frameworks. Python has a
+ simple class model, which facilitates inheritance. It has
+ dynamic typing, which means code needs to assume less. Python
+ also has built-in memory management, which means application
+ code does not need to track ownership. Thus, when writing a new
+ application, a programmer often finds himself writing a
+ framework to make writing this kind of application easier.
+ Twisted evolved from the need to write high-performance
+ interoperable servers in Python, and making them easy to use
+ (and difficult to use incorrectly).</p>
+
+ <p>There are three ways to write network programs:</p>
+
+ <ol>
+ <li>Handle each connection in a separate process</li>
+
+ <li>Handle each connection in a separate thread</li>
+
+ <li>Use non-blocking system calls to handle all connections
+ in one thread.</li>
+ </ol>
+
+ <p>When dealing with many connections in one thread, the
+ scheduling is the responsibility of the application, not the
+ operating system, and is usually implemented by calling a
+ registered function when each connection is ready to for
+ reading or writing -- commonly known as event-driven, or
+ callback-based, programming.</p>
+
+ <p>Since multi-threaded programming is often tricky, even with
+ high level abstractions, and since forking Python processes has
+ many disadvantages, like Python's reference counting not
+ playing well with copy-on-write and problems with shared state,
+ it was felt the best option was an event-driven framework. A
+ benefit of such approach is that by letting other event-driven
+ frameworks take over the main loop, server and client code are
+ essentially the same - making peer-to-peer a reality. While
+ Twisted includes its own event loop, Twisted can already
+ interoperate with <code>GTK+</code>'s and <code>Tk</code>'s
+ mainloops, as well as provide an emulation of event-based I/O
+ for Jython (specific support for the Swing toolkit is planned).
+ Client code is never aware of the loop it is running under, as
+ long as it is using Twisted's interface for registering for
+ interesting events.</p>
+
+ <p>Some examples of programs which were written using the
+ Twisted framework are <code>twisted.web</code> (a web server),
+ <code>twisted.mail</code> (a mail server, supporting both SMTP
+ and POP3, as well as relaying), <code>twisted.words</code> (a
+ chat application supporting integration between a variety of IM
+ protocols, like IRC, AOL Instant Messenger's TOC and
+ Perspective Broker, a remote-object protocol native to
+ Twisted), <code>im</code> (an instant messenger which connects
+ to twisted.words) and <code>faucet</code> (a GUI client for the
+ <code>twisted.reality</code> interactive-fiction framework).
+ Twisted can be useful for any network or GUI application
+ written in Python.</p>
+
+ <p>However, event-driven programming still contains some tricky
+ aspects. As each callback must be finished as soon as possible,
+ it is not possible to keep persistent state in function-local
+ variables. In addition, some programming techniques, such as
+ recursion, are impossible to use. Event-driven programming has
+ a reputation of being hard to use due to the frequent need to
+ write state machines. Twisted was built with the assumption
+ that with the right library, event-driven programming is easier
+ then multi-threaded programming. Twisted aims to be that
+ library.</p>
+
+ <p>Twisted includes both high-level and low-level support for
+ protocols. Most protocol implementation by twisted are in a
+ package which tries to implement "mechanisms, not policy". On
+ top of those implementations, Twisted includes usable
+ implementations of those protocols: for example, connecting the
+ abstract HTTP protocol handler to a concrete resource-tree, or
+ connecting the abstract mail protocol handler to deliver mail
+ to maildirs according to domains. Twisted tries to come with as
+ much functionality as possible out of the box, while not
+ constraining a programmer to a choice between using a
+ possibly-inappropriate class and rewriting the non-interesting
+ parts himself.</p>
+
+ <p>Twisted also includes Perspective Broker, a simple
+ remote-object framework, which allows Twisted servers to be
+ divided into separate processes as the end deployer (rather
+ then the original programmer) finds most convenient. This
+ allows, for example, Twisted web servers to pass requests for
+ specific URLs with co-operating servers so permissions are
+ granted according to the need of the specific application,
+ instead of being forced into giving all the applications all
+ permissions. The co-operation is truly symmetrical, although
+ typical deployments (such as the one which the Twisted web site
+ itself uses) use a master/slave relationship.</p>
+
+ <p>Twisted is not alone in the niche of a Python network
+ framework. One of the better known frameworks is Medusa. Medusa
+ is used, among other things, as Zope's native server serving
+ HTTP, FTP and other protocols. However, Medusa is no longer
+ under active development, and the Twisted development team had
+ a number of goals which would necessitate a rewrite of large
+ portions of Medusa. Twisted seperates protocols from the
+ underlying transport layer. This seperation has the advantages
+ of resuability (for example, using the same clients and servers
+ over SSL) and testability (because it is easy to test the
+ protocol with a much lighter test harness) among others.
+ Twisted also has a very flexible main-loop which can
+ interoperate with third-party main-loops, making it usable in
+ GUI programs too.</p>
+
+ <h3>Complementing Python</h3>
+
+ <p>Python comes out of the box with "batteries included".
+ However, it seems that many Python projects rewrite some basic
+ parts: logging to files, parsing options and high level
+ interfaces to reflection. When the Twisted project found itself
+ rewriting those, it moved them into a separate subpackage,
+ which does not depend on the rest of the twisted framework.
+ Hopefully, people will use <code>twisted.python</code> more and
+ solve interesting problems instead. Indeed, it is one of
+ Twisted's goals to serve as a repository for useful Python
+ code.</p>
+
+ <p>One useful module is <code>twisted.python.reflect</code>,
+ which has methods like <code>prefixedMethods</code>, which
+ returns all methods with a specific prefix. Even though some
+ modules in Python itself implement such functionality (notably,
+ <code>urllib2</code>), they do not expose it as a function
+ usable by outside code. Another useful module is
+ <code>twisted.python.hook</code>, which can add pre-hooks and
+ post-hooks to methods in classes.</p>
+
+ <blockquote>
+<pre class="python">
+# Add all method names beginning with opt_ to the given
+# dictionary. This cannot be done with dir(), since
+# it does not search in superclasses
+dct = {}
+reflect.addMethodNamesToDict(self.__class__, dct, "opt_")
+
+# Sum up all lists, in the given class and superclasses,
+# which have a given name. This gives us "different class
+# semantics": attributes do not override, but rather append
+flags = []
+reflect.accumulateClassList(self.__class__, 'optFlags', flags)
+
+# Add lock-acquire and lock-release to all methods which
+# are not multi-thread safe
+for methodName in klass.synchronized:
+ hook.addPre(klass, methodName, _synchPre)
+ hook.addPost(klass, methodName, _synchPost)
+
+</pre>
+
+ <h6>Listing 1: Using <code>twisted.python.reflect</code> and
+ <code>twisted.python.hook</code></h6>
+ </blockquote>
+
+ <p>The <code>twisted.python</code> subpackage also contains a
+ high-level interface to getopt which supplies as much power as
+ plain getopt while avoiding long
+ <code>if</code>/<code>elif</code> chains and making many common
+ cases easier to use. It uses the reflection interfaces in
+ <code>twisted.python.reflect</code> to find which options the
+ class is interested in, and constructs the argument to
+ <code>getopt</code>. Since in the common case options' values
+ are just saved in instance attributes, it is very easy to
+ indicate interest in such options. However, for the cases
+ custom code needs to be run for an option (for example,
+ counting how many <code>-v</code> options were given to
+ indicate verbosity level), it will call a method which is named
+ correctly.</p>
+
+ <blockquote>
+<pre class="python">
+class ServerOptions(usage.Options):
+ # Those are (short and long) options which
+ # have no argument. The corresponding attribute
+ # will be true iff this option was given
+ optFlags = [['nodaemon','n'],
+ ['profile','p'],
+ ['threaded','t'],
+ ['quiet','q'],
+ ['no_save','o']]
+ # This are options which require an argument
+ # The default is used if no such option was given
+ # Note: since options can only have string arguments,
+ # putting a non-string here is a reliable way to detect
+ # whether the option was given
+ optStrings = [['logfile','l',None],
+ ['file','f','twistd.tap'],
+ ['python','y',''],
+ ['pidfile','','twistd.pid'],
+ ['rundir','d','.']]
+
+ # For methods which can be called multiple times
+ # or have other unusual semantics, a method will be called
+ # Twisted assumes that the option needs an argument if and only if
+ # the method is defined to accept an argument.
+ def opt_plugin(self, pkgname):
+ pkg = __import__(pkgname)
+ self.python = os.path.join(os.path.dirname(
+ os.path.abspath(pkg.__file__)), 'config.tac')
+
+ # Most long options based on methods are aliased to short
+ # options. If there is only one letter, Twisted knows it is a short
+ # option, so it is "-g", not "--g"
+ opt_g = opt_plugin
+
+try:
+ config = ServerOptions()
+ config.parseOptions()
+except usage.error, ue:
+ print "%s: %s" % (sys.argv[0], ue)
+ sys.exit(1)
+</pre>
+
+ <h6>Listing 2: <code>twistd</code>'s Usage Code</h6>
+ </blockquote>
+
+ <p>Unlike <code>getopt</code>, Twisted has a useful abstraction
+ for the non-option arguments: they are passed as arguments to
+ the <code>parsedArgs</code> method. This means too many
+ arguments, or too few, will cause a usage error, which will be
+ flagged. If an unknown number of arguments is desired,
+ explicitly using a tuple catch-all argument will work.</p>
+
+ <h3>Configuration</h3>
+
+ <p>The formats of configuration files have shown two visible
+ trends over the years. On the one hand, more and more
+ programmability has been added, until sometimes they become a
+ new language. The extreme end of this trend is using a regular
+ programming language, such as Python, as the configuration
+ language. On the other hand, some configuration files became
+ more and more machine editable, until they become a miniature
+ database formates. The extreme end of that trend is using a
+ generic database tool.</p>
+
+ <p>Both trends stem from the same rationale -- the need to use
+ a powerful general purpose tool instead of hacking domain
+ specific languages. Domain specific languages are usually
+ ad-hoc and not well designed, having neither the power of
+ general purpose languages nor the predictable machine editable
+ format of generic databases.</p>
+
+ <p>Twisted combines these two trends. It can read the
+ configuration either from a Python file, or from a pickled
+ file. To some degree, it integrates the approaches by
+ auto-pickling state on shutdown, so the configuration files can
+ migrate from Python into pickles. Currently, there is no way to
+ go back from pickles to equivalent Python source, although it
+ is planned for the future. As a proof of concept, the RPG
+ framework Twisted Reality already has facilities for creating
+ Python source which evaluates into a given Python object.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import main
+from twisted.web import proxy, server
+site = server.Site(proxy.ReverseProxyResource('www.yahoo.com', 80, '/'))
+application = main.Application('web-proxy')
+application.listenOn(8080, site)
+</pre>
+
+ <h6>Listing 3: The configuration file for a reverse web
+ proxy</h6>
+ </blockquote>
+
+ <p>Twisted's main program, <code>twistd</code>, can receive
+ either a pickled <code>twisted.internet.main.Application</code>
+ or a Python file which defines a variable called
+ <code>application</code>. The application can be saved at any
+ time by calling its <code>save</code> method, which can take an
+ optional argument to save to a different file name. It would be
+ fairly easy, for example, to have a Twisted server which saves
+ the application every few seconds to a file whose name depends
+ on the time. Usually, however, one settles for the default
+ behavior which saves to a <code>shutdown</code> file. Then, if
+ the shutdown configuration proves suitable, the regular pickle
+ is replaced by the shutdown file. Hence, on the fly
+ configuration changes, regardless of complexity, can always
+ persist.</p>
+
+ <p>There are several client/server protocols which let a
+ suitably privileged user to access to application variable and
+ change it on the fly. The first, and least common denominator,
+ is telnet. The administrator can telnet into twisted, and issue
+ Python statements to her heart's content. For example, one can
+ add ports to listen on to the application, reconfigure the web
+ servers and various other ways by simple accessing
+ <code>__main__.application</code>. Some proof of concepts for a
+ simple suite of command-line utilities to control a Twisted
+ application were written, including commands which allow an
+ administrator to shut down the server or save the current state
+ to a tap file. These are especially useful on Microsoft
+ Windows(tm) platforms, where the normal UNIX way of
+ communicating shutdown requests via signals are less
+ reliable.</p>
+
+ <p>If reconfiguration on the fly is not necessary, Python
+ itself can be used as the configuration editor. Loading the
+ application is as simple as unpickling it, and saving it is
+ done by calling its <code>save</code> method. It is quite easy
+ to add more services or change existing ones from the Python
+ interactive mode.</p>
+
+ <p>A more sophisticated way to reconfigure the application on
+ the fly is via the manhole service. Manhole is a client/server
+ protocol based on top of Perspective Broker, Twisted's
+ translucent remote-object protocol which will be covered later.
+ Manhole has a graphical client called <code>gtkmanhole</code>
+ which can access the server and change its state. Since Twisted
+ is modular, it is possible to write more services for user
+ friendly configuration. For example, through-the-web
+ configuration is planned for several services, notably
+ mail.</p>
+
+ <p>For cases where a third party wants to distribute both the
+ code for a server and a ready to run configuration file, there
+ is the plugin configuration. Philosophically similar to the
+ <code>--python</code> option to <code>twistd</code>, it
+ simplifies the distribution process. A plugin is an archive
+ which is ready to be unpacked into the Python module path. In
+ order to keep a clean tree, <code>twistd</code> extends the
+ module path with some Twisted-specific paths, like the
+ directory <code>TwistedPlugins</code> in the user's home
+ directory. When a plugin is unpacked, it should be a Python
+ package which includes, alongside <code>__init__.py</code> a
+ file named <code>config.tac</code>. This file should define a
+ variable named <code>application</code>, in a similar way to
+ files loaded with <code>--python</code>. The plugin way of
+ distributing configurations is meant to reduce the temptation
+ to put large amount of codes inside the configuration file
+ itself.</p>
+
+ <p>Putting class and function definition inside the
+ configuration files would make the persistent servers which are
+ auto-generated on shutdown useless, since they would not have
+ access to the classes and functions defined inside the
+ configuration file. Thus, the plugin method is intended so
+ classes and functions can still be in regular, importable,
+ Python modules, but still allow third parties distribute
+ powerful configurations. Plugins are used by some of the
+ Twisted Reality virtual worlds.</p>
+
+ <h3>Ports, Protocol and Protocol Factories</h3>
+
+ <p><code>Port</code> is the Twisted class which represents a
+ socket listening on a port. Currently, twisted supports both
+ internet and unix-domain sockets, and there are SSL classes
+ with identical interface. A <code>Port</code> is only
+ responsible for handling the transfer layer. It calls
+ <code>accept</code> on the socket, checks that it actually
+ wants to deal with the connection and asks its factory for a
+ protocol. The factory is usually a subclass of
+ <code>twisted.protocols.protocol.Factory</code>, and its most
+ important method is <code>buildProtocol</code>. This should
+ return something that adheres to the protocol interface, and is
+ usually a subclass of
+ <code>twisted.protocols.protocol.Protocol</code>.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.protocols import protocol
+from twisted.internet import main, tcp
+
+class Echo(protocol.Protocol):
+ def dataReceived(self, data):
+ self.transport.write(data)
+
+factory = protocol.Factory()
+factory.protocol = Echo
+port = tcp.Port(8000, factory)
+app = main.Application("echo")
+app.addPort(port)
+app.run()
+</pre>
+
+ <h6>Listing 4: A Simple Twisted Application</h6>
+ </blockquote>
+
+ <p>The factory is responsible for two tasks: creating new
+ protocols, and keeping global configuration and state. Since
+ the factory builds the new protocols, it usually makes sure the
+ protocols have a reference to it. This allows protocols to
+ access, and change, the configuration. Keeping state
+ information in the factory is the primary reason for keeping an
+ abstraction layer between ports and protocols. Examples of
+ configuration information is the root directory of a web server
+ or the user database of a telnet server. Note that it is
+ possible to use the same factory in two different Ports. This
+ can be used to run the same server bound to several different
+ addresses but not to all of them, or to run the same server on
+ a TCP socket and a UNIX domain sockets.</p>
+
+ <p>A protocol begins and ends its life with
+ <code>connectionMade</code> and <code>connectionLost</code>;
+ both are called with no arguments. <code>connectionMade</code>
+ is called when a connection is first established. By then, the
+ protocol has a <code>transport</code> attribute. The
+ <code>transport</code> attribute is a <code>Transport</code> -
+ it supports <code>write</code> and <code>loseConnection</code>.
+ Both these methods never block: <code>write</code> actually
+ buffers data which will be written only when the transport is
+ signalled ready to for writing, and <code>loseConnection</code>
+ marks the transport for closing as soon as there is no buffered
+ data. Note that transports do <em>not</em> have a
+ <code>read</code> method: data arrives when it arrives, and the
+ protocol must be ready for its <code>dataReceived</code>
+ method, or its <code>connectionLost</code> method, to be
+ called. The transport also supports a <code>getPeer</code>
+ method, which returns parameters about the other side of the
+ transport. For TCP sockets, this includes the remote IP and
+ port.</p>
+
+ <blockquote>
+<pre class="python">
+# A tcp port-forwarder
+# A StupidProtocol sends all data it gets to its peer.
+# A StupidProtocolServer connects to the host/port,
+# and initializes the client connection to be its peer
+# and itself to be the client's peer
+from twisted.protocols import protocol
+
+class StupidProtocol(protocol.Protocol):
+ def connectionLost(self): self.peer.loseConnection();del self.peer
+ def dataReceived(self, data): self.peer.write(data)
+
+class StupidProtocolServer(StupidProtocol):
+ def connectionMade(self):
+ clientProtocol = StupidProtocol()
+ clientProtocol.peer = self.transport
+ self.peer = tcp.Client(self.factory.host, self.factory.port,
+ clientProtocol)
+
+# Create a factory which creates StupidProtocolServers, and
+# has the configuration information they assume
+def makeStupidFactory(host, port):
+ factory = protocol.Factory()
+ factory.host, factory.port = host, port
+ factory.protocol = StupidProtocolServer
+ return factory
+</pre>
+
+ <h6>Listing 5: TCP forwarder code</h6>
+ </blockquote>
+
+ <h3>The Event Loop</h3>
+
+ <p>While Twisted has the ability to let other event loops take
+ over for integration with GUI toolkits, it usually uses its own
+ event loop. The event loop code uses global variables to
+ maintain interested readers and writers, and uses Python's
+ <code>select()</code> function, which can accept any object
+ which has a <code>fileno()</code> method, not only raw file
+ descriptors. Objects can use the event loop interface to
+ indicate interest in either reading to or writing from a given
+ file descriptor. In addition, for those cases where time-based
+ events are needed (for example, queue flushing or periodic POP3
+ downloads), Twisted has a mechanism for repeating events at
+ known delays. While far from being real-time, this is enough
+ for most programs' needs.</p>
+
+ <h3>Going Higher Level</h3>
+
+ <p>Unfortunately, handling arbitrary data chunks is a hard way
+ to code a server. This is why twisted has many classes sitting
+ in submodules of the twisted.protocols package which give
+ higher level interface to the data. For line oriented
+ protocols, <code>LineReceiver</code> translates the low-level
+ <code>dataReceived</code> events into <code>lineReceived</code>
+ events. However, the first naive implementation of
+ <code>LineReceiver</code> proved to be too simple. Protocols
+ like HTTP/1.1 or Freenet have packets which begin with header
+ lines that include length information, and then byte streams.
+ <code>LineReceiver</code> was rewritten to have a simple
+ interface for switching at the protocol layer between
+ line-oriented parts and byte-stream parts.</p>
+
+ <p>Another format which is gathering popularity is Dan J.
+ Bernstein's netstring format. This format keeps ASCII text as
+ ASCII, but allows arbitrary bytes (including nulls and
+ newlines) to be passed freely. However, netstrings were never
+ designed to be used in event-based protocols where over-reading
+ is unavoidable. Twisted makes sure no user will have to deal
+ with the subtle problems handling netstrings in event-driven
+ programs by providing <code>NetstringReceiver</code>.</p>
+
+ <p>For even higher levels, there are the protocol-specific
+ protocol classes. These translate low-level chunks into
+ high-level events such as "HTTP request received" (for web
+ servers), "approve destination address" (for mail servers) or
+ "get user information" (for finger servers). Many RFCs have
+ been thus implemented for Twisted (at latest count, more then
+ 12 RFCs have been implemented). One of Twisted's goals is to be
+ a repository of event-driven implementations for various
+ protocols in Python.</p>
+
+ <blockquote>
+<pre class="python">
+class DomainSMTP(SMTP):
+
+ def validateTo(self, helo, destination):
+ try:
+ user, domain = string.split(destination, '@', 1)
+ except ValueError:
+ return 0
+ if not self.factory.domains.has_key(domain):
+ return 0
+ if not self.factory.domains[domain].exists(user, domain, self):
+ return 0
+ return 1
+
+ def handleMessage(self, helo, origin, recipients, message):
+ # No need to check for existence -- only recipients which
+ # we approved at the validateTo stage are passed here
+ for recipient in recipients:
+ user, domain = string.split(recipient, '@', 1)
+ self.factory.domains[domain].saveMessage(origin, user, message,
+ domain)
+</pre>
+
+ <h6>Listing 6: Implementation of virtual domains using the
+ SMTP protocol class</h6>
+ </blockquote>
+
+ <p>Copious documentation on writing new protocol abstraction
+ exists, since this is the largest amount of code written --
+ much like most operating system code is device drivers. Since
+ many different protocols have already been implemented, there
+ are also plenty of examples to draw on. Usually implementing
+ the client-side of a protocol is particularly challenging,
+ since protocol designers tend to assume much more state kept on
+ the client side of a connection then on the server side.</p>
+
+ <h3>The <code>twisted.tap</code> Package and
+ <code>mktap</code></h3>
+
+ <p>Since one of Twisted's configuration formats are pickles,
+ which are tricky to edit by hand, Twisted evolved a framework
+ for creating such pickles. This framework is contained in the
+ <code>twisted.tap</code> package and the <code>mktap</code>
+ script. New servers, or new ways to configure existing servers,
+ can easily participate in the twisted.tap framework by creating
+ a <code>twisted.tap</code> submodule.</p>
+
+ <p>All <code>twisted.tap</code> submodules must conform to a
+ rigid interface. The interface defines functions to accept the
+ command line parameters, and functions to take the processed
+ command line parameters and add servers to
+ <code>twisted.main.internet.Application</code>. Existing
+ <code>twisted.tap</code> submodules use
+ <code>twisted.python.usage</code>, so the command line format
+ is consistent between different modules.</p>
+
+ <p>The <code>mktap</code> utility gets some generic options,
+ and then the name of the server to build. It imports a
+ same-named <code>twisted.tap</code> submodule, and lets it
+ process the rest of the options and parameters. This makes sure
+ that the process configuring the <code>main.Application</code>
+ is agnostic for where it is used. This allowed
+ <code>mktap</code> to grow the <code>--append</code> option,
+ which appends to an existing pickle rather then creating a new
+ one. This option is frequently used to post-add a telnet server
+ to an application, for net-based on the fly configuration
+ later.</p>
+
+ <p>When running <code>mktap</code> under UNIX, it saves the
+ user id and group id inside the tap. Then, when feeding this
+ tap into <code>twistd</code>, it changes to this user/group id
+ after binding the ports. Such a feature is necessary in any
+ production-grade server, since ports below 1024 require root
+ privileges to use on UNIX -- but applications should not run as
+ root. In case changing to the specified user causes difficulty
+ in the build environment, it is also possible to give those
+ arguments to <code>mktap</code> explicitly.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import tcp, stupidproxy
+from twisted.python import usage
+
+usage_message = """
+usage: mktap stupid [OPTIONS]
+
+Options are as follows:
+ --port &lt;#&gt;, -p: set the port number to &lt;#&gt;.
+ --host &lt;host&gt;, -h: set the host to &lt;host&gt;
+ --dest_port &lt;#&gt;, -d: set the destination port to &lt;#&gt;
+"""
+
+class Options(usage.Options):
+ optStrings = [["port", "p", 6666],
+ ["host", "h", "localhost"],
+ ["dest_port", "d", 6665]]
+
+def getPorts(app, config):
+ s = stupidproxy.makeStupidFactory(config.host, int(config.dest_port))
+ return [(int(config.port), s)]
+</pre>
+
+ <h6>Listing 7: <code>twisted.tap.stupid</code></h6>
+ </blockquote>
+
+ <p>The <code>twisted.tap</code> framework is one of the reasons
+ servers can be set up with little knowledge and time. Simply
+ running <code>mktap</code> with arguments can bring up a web
+ server, a mail server or an integrated chat server -- with
+ hardly any need for maintainance. As a working
+ proof-on-concept, the <code>tap2deb</code> utility exists to
+ wrap up tap files in Debian packages, which include scripts for
+ running and stopping the server and interact with
+ <code>init(8)</code> to make sure servers are automatically run
+ on start-up. Such programs can also be written to interface
+ with the Red Hat Package Manager or the FreeBSD package
+ management systems.</p>
+
+ <blockquote>
+<pre class="shell">
+% mktap --uid 33 --gid 33 web --static /var/www --port 80
+% tap2deb -t web.tap -m 'Moshe Zadka &lt;moshez@debian.org&gt;'
+% su
+password:
+# dpkg -i .build/twisted-web_1.0_all.deb
+</pre>
+
+ <h6>Listing 8: Bringing up a web server on a Debian
+ system</h6>
+ </blockquote>
+
+ <h3>Multi-thread Support</h3>
+
+ <p>Sometimes, threads are unavoidable or hard to avoid. Many
+ legacy programs which use threads want to use Twisted, and some
+ vendor APIs have no non-blocking version -- for example, most
+ database systems' API. Twisted can work with threads, although
+ it supports only one thread in which the main select loop is
+ running. It can use other threads to simulate non-blocking API
+ over a blocking API -- it spawns a thread to call the blocking
+ API, and when it returns, the thread calls a callback in the
+ main thread. Threads can call callbacks in the main thread
+ safely by adding those callbacks to a list of pending events.
+ When the main thread is between select calls, it searches
+ through the list of pending events, and executes them. This is
+ used in the <code>twisted.enterprise</code> package to supply
+ an event driven interfaces to databases, which uses Python's DB
+ API.</p>
+
+ <p>Twisted tries to optimize for the common case -- no threads.
+ If there is need for threads, a special call must be made to
+ inform the <code>twisted.python.threadable</code> module that
+ threads will be used. This module is implemented differently
+ depending on whether threads will be used or not. The decision
+ must be made before importing any modules which use threadable,
+ and so is usually done in the main application. For example,
+ <code>twistd</code> has a command line option to initialize
+ threads.</p>
+
+ <p>Twisted also supplies a module which supports a threadpool,
+ so the common task of implementing non-blocking APIs above
+ blocking APIs will be both easy and efficient. Threads are kept
+ in a pool, and dispatch requests are done by threads which are
+ not working. The pool supports a maximum amount of threads, and
+ will throw exceptions when there are more requests than
+ allowable threads.</p>
+
+ <p>One of the difficulties about multi-threaded systems is
+ using locks to avoid race conditions. Twisted uses a mechanism
+ similar to Java's synchronized methods. A class can declare a
+ list of methods which cannot safely be called at the same time
+ from two different threads. A function in threadable then uses
+ <code>twisted.python.hook</code> to transparently add
+ lock/unlock around these methods. This allows Twisted classes
+ to be written without thought about threading, except for one
+ localized declaration which does not entail any performance
+ penalty for the single-threaded case.</p>
+
+ <h3>Twisted Mail Server</h3>
+
+ <p>Mail servers have a history of security flaws. Sendmail is
+ by now the poster boy of security holes, but no mail servers,
+ bar maybe qmail, are free of them. Like Dan Bernstein of qmail
+ fame said, mail cannot be simply turned off -- even the
+ simplest organization needs a mail server. Since Twisted is
+ written in a high-level language, many problems which plague
+ other mail servers, notably buffer overflows, simply do not
+ exist. Other holes are avoidable with correct design. Twisted
+ Mail is a project trying to see if it is possible to write a
+ high quality high performance mail server entirely in
+ Python.</p>
+
+ <p>Twisted Mail is built on the SMTP server and client protocol
+ classes. While these present a level of abstraction from the
+ specific SMTP line semantics, they do not contain any message
+ storage code. The SMTP server class does know how to divide
+ responsibility between domains. When a message arrives, it
+ analyzes the recipient's address, tries matching it with one of
+ the registered domain, and then passes validation of the
+ address and saving the message to the correct domain, or
+ refuses to handle the message if it cannot handle the domain.
+ It is possible to specify a catch-all domain, which will
+ usually be responsible for relaying mails outwards.</p>
+
+ <p>While correct relaying is planned for the future, at the
+ moment we have only so-called "smarthost" relaying. All e-mail
+ not recognized by a local domain is relayed to a single outside
+ upstream server, which is supposed to relay the mail further.
+ This is the configuration for most home machines, which are
+ Twisted Mail's current target audience.</p>
+
+ <p>Since the people involved in Twisted's development were
+ reluctant to run code that runs as a super user, or with any
+ special privileges, it had to be considered how delivery of
+ mail to users is possible. The solution decided upon was to
+ have Twisted deliver to its own directory, which should have
+ very strict permissions, and have users pull the mail using
+ some remote mail access protocol like POP3. This means only a
+ user would write to his own mail box, so no security holes in
+ Twisted would be able to adversely affect a user.</p>
+
+ <p>Future plans are to use a Perspective Broker-based service
+ to hand mail to users to a personal server using a UNIX domain
+ socket, as well as to add some more conventional delivery
+ methods, as scary as they may be.</p>
+
+ <p>Because the default configuration of Twisted Mail is to be
+ an integrated POP3/SMTP servers, it is ideally suited for the
+ so-called POP toaster configuration, where there are a
+ multitude of virtual users and domains, all using the same IP
+ address and computer to send and receive mails. It is fairly
+ easy to configure Twisted as a POP toaster. There are a number
+ of deployment choices: one can append a telnet server to the
+ tap for remote configuration, or simple scripts can add and
+ remove users from the user database. The user database is saved
+ as a directory, where file names are keys and file contents are
+ values, so concurrency is not usually a problem.</p>
+
+ <blockquote>
+<pre class="shell">
+% mktap mail -d foobar.com=$HOME/Maildir/ -u postmaster=secret -b \
+ -p 110 -s 25
+% twistd -f mail.tap
+
+</pre>
+
+ <h6>Bringing up a simple mail-server</h6>
+ </blockquote>
+
+ <p>Twisted's native mail storage format is Maildir, a format
+ that requires no locking and is safe and atomic. Twisted
+ supports a number of standardized extensions to Maildir,
+ commonly known as Maildir++. Most importantly, it supports
+ deletion as simply moving to a subfolder named
+ <code>Trash</code>, so mail is recoverable if accessed through
+ a protocol which allows multiple folders, like IMAP. However,
+ Twisted itself currently does not support any such protocol
+ yet.</p>
+
+ <h3>Introducing Perspective Broker</h3>
+
+ <h4>All the World's a Game</h4>
+
+ <p>Twisted was originally designed to support multi-player
+ games; a simulated "real world" environment. Experience with
+ game systems of that type is enlightening as to the nature of
+ computing on the whole. Almost all services on a computer are
+ modeled after some simulated real-world activity. For example,
+ e-"mail", or "document publishing" on the web. Even
+ "object-oriented" programming is based around the notion that
+ data structures in a computer simulate some analogous
+ real-world objects.</p>
+
+ <p>All such networked simulations have a few things in common.
+ They each represent a service provided by software, and there
+ is usually some object where "global" state is kept. Such a
+ service must provide an authentication mechanism. Often, there
+ is a representation of the authenticated user within the
+ context of the simulation, and there are also objects aside
+ from the user and the simulation itself that can be
+ accessed.</p>
+
+ <p>For most existing protocols, Twisted provides these
+ abstractions through <code>twisted.internet.passport</code>.
+ This is so named because the most important common
+ functionality it provides is authentication. A simulation
+ "world" as described above -- such as an e-mail system,
+ document publishing archive, or online video game -- is
+ represented by subclass of <code>Service</code>, the
+ authentication mechanism by an <code>Authorizer</code> (which
+ is a set of <code>Identities</code>), and the user of the
+ simulation by a <code>Perspective</code>. Other objects in the
+ simulation may be represented by arbitrary python objects,
+ depending upon the implementation of the given protocol.</p>
+
+ <p>New problem domains, however, often require new protocols,
+ and re-implementing these abstractions each time can be
+ tedious, especially when it's not necessary. Many efforts have
+ been made in recent years to create generic "remote object" or
+ "remote procedure call" protocols, but in developing Twisted,
+ these protocols were found to require too much overhead in
+ development, be too inefficient at runtime, or both.</p>
+
+ <p>Perspective Broker is a new remote-object protocol designed
+ to be lightweight and impose minimal constraints upon the
+ development process and use Python's dynamic nature to good
+ effect, but still relatively efficient in terms of bandwidth
+ and CPU utilization. <code>twisted.spread.pb</code> serves as a
+ reference implementation of the protocol, but implementation of
+ Perspective Broker in other languages is already underway.
+ <code>spread</code> is the <code>twisted</code> subpackage
+ dealing with remote calls and objects, and has nothing to do
+ with the <code>spread</code> toolkit.</p>
+
+ <p>Perspective Broker extends
+ <code>twisted.internet.passport</code>'s abstractions to be
+ concrete objects rather than design patterns. Rather than
+ having a <code>Protocol</code> implementation translate between
+ sequences of bytes and specifically named methods (as in the
+ other Twisted <code>Protocols</code>), Perspective Broker
+ defines a direct mapping between network messages and
+ quasi-arbitrary method calls.</p>
+
+ <h3>Translucent, not Transparent</h3>
+
+ <p>In a server application where a large number of clients may
+ be interacting at once, it is not feasible to have an
+ arbitrarily large number of OS threads blocking and waiting for
+ remote method calls to return. Additionally, the ability for
+ any client to call any method of an object would present a
+ significant security risk. Therefore, rather than attempting to
+ provide a transparent interface to remote objects,
+ <code>twisted.spread.pb</code> is "translucent", meaning that
+ while remote method calls have different semantics than local
+ ones, the similarities in semantics are mirrored by
+ similarities in the syntax. Remote method calls impose as
+ little overhead as possible in terms of volume of code, but "as
+ little as possible" is unfortunately not "nothing".</p>
+
+ <p><code>twisted.spread.pb</code> defines a method naming
+ standard for each type of remotely accessible object. For
+ example, if a client requests a method call with an expression
+ such as <code>myPerspective.doThisAction()</code>, the remote
+ version of <code>myPerspective</code> would be sent the message
+ <code>perspective_doThisAction</code>. Depending on the manner
+ in which an object is accessed, other method prefixes may be
+ <code>observe_</code>, <code>view_</code>, or
+ <code>remote_</code>. Any method present on a remotely
+ accessible object, and named appropriately, is considered to be
+ published -- since this is accomplished with
+ <code>getattr</code>, the definition of "present" is not just
+ limited to methods defined on the class, but instances may have
+ arbitrary callable objects associated with them as long as the
+ name is correct -- similarly to normal python objects.</p>
+
+ <p>Remote method calls are made on remote reference objects
+ (instances of <code>pb.RemoteReference</code>) by calling a
+ method with an appropriate name. However, that call will not
+ block -- if you need the result from a remote method call, you
+ pass in one of the two special keyword arguments to that method
+ -- <code>pbcallback</code> or <code>pberrback</code>.
+ <code>pbcallback</code> is a callable object which will be
+ called when the result is available, and <code>pberrback</code>
+ is a callable object which will be called if there was an
+ exception thrown either in transmission of the call or on the
+ remote side.</p>
+
+ <p>In the case that neither <code>pberrback</code> or
+ <code>pbcallback</code> is provided,
+ <code>twisted.spread.pb</code> will optimize network usage by
+ not sending confirmations of messages.</p>
+
+ <blockquote>
+<pre class="python">
+# Server Side
+class MyObject(pb.Referenceable):
+ def remote_doIt(self):
+ return "did it"
+
+# Client Side
+ ...
+ def myCallback(result):
+ print result # result will be 'did it'
+ def myErrback(stacktrace):
+ print 'oh no, mr. bill!'
+ print stacktrace
+ myRemoteReference.doIt(pbcallback=myCallback,
+ pberrback=myErrback)
+</pre>
+
+ <h6>Listing 9: A remotely accessible object and accompanying
+ call</h6>
+ </blockquote>
+
+ <h3>Different Behavior for Different Perspectives</h3>
+
+ <p>Considering the problem of remote object access in terms of
+ a simulation demonstrates a requirement for the knowledge of an
+ actor with certain actions or requests. Often, when processing
+ message, it is useful to know who sent it, since different
+ results may be required depending on the permissions or state
+ of the caller.</p>
+
+ <p>A simple example is a game where certain an object is
+ invisible, but players with the "Heightened Perception"
+ enchantment can see it. When answering the question "What
+ objects are here?" it is important for the room to know who is
+ asking, to determine which objects they can see. Parallels to
+ the differences between "administrators" and "users" on an
+ average multi-user system are obvious.</p>
+
+ <p>Perspective Broker is named for the fact that it does not
+ broker only objects, but views of objects. As a user of the
+ <code>twisted.spread.pb</code> module, it is quite easy to
+ determine the caller of a method. All you have to do is
+ subclass <code>Viewable</code>.</p>
+
+ <blockquote>
+<pre class="python">
+# Server Side
+class Greeter(pb.Viewable):
+ def view_greet(self, actor):
+ return "Hello %s!\n" % actor.perspectiveName
+
+# Client Side
+ ...
+ remoteGreeter.greet(pbcallback=sys.stdout.write)
+ ...
+</pre>
+
+ <h6>Listing 10: An object responding to its calling
+ perspective</h6>
+ </blockquote>
+ Before any arguments sent by the client, the actor
+ (specifically, the Perspective instance through which this
+ object was retrieved) will be passed as the first argument to
+ any <code>view_xxx</code> methods.
+
+ <h3>Mechanisms for Sharing State</h3>
+
+ <p>In a simulation of any decent complexity, client and server
+ will wish to share structured data. Perspective Broker provides
+ a mechanism for both transferring (copying) and sharing
+ (caching) that state.</p>
+
+ <p>Whenever an object is passed as an argument to or returned
+ from a remote method call, that object is serialized using
+ <code>twisted.spread.jelly</code>; a serializer similar in some
+ ways to Python's native <code>pickle</code>. Originally,
+ <code>pickle</code> itself was going to be used, but there were
+ several security issues with the <code>pickle</code> code as it
+ stands. It is on these issues of security that
+ <code>pickle</code> and <code>twisted.spread.jelly</code> part
+ ways.</p>
+
+ <p>While <code>twisted.spread.jelly</code> handles a few basic
+ types such as strings, lists, dictionaries and numbers
+ automatically, all user-defined types must be registered both
+ for serialization and unserialization. This registration
+ process is necessary on the sending side in order to determine
+ if a particular object is shared, and whether it is shared as
+ state or behavior. On the receiving end, it's necessary to
+ prevent arbitrary code from being run when an object is
+ unserialized -- a significant security hole in
+ <code>pickle</code> for networked applications.</p>
+
+ <p>On the sending side, the registration is accomplished by
+ making the object you want to serialize a subclass of one of
+ the "flavors" of object that are handled by Perspective Broker.
+ A class may be <code>Referenceable</code>,
+ <code>Viewable</code>, <code>Copyable</code> or
+ <code>Cacheable</code>. These four classes correspond to
+ different ways that the object will be seen remotely.
+ Serialization flavors are mutually exclusive -- these 4 classes
+ may not be mixed in with each other.</p>
+
+ <ul>
+ <li><code>Referenceable</code>: The remote side will refer to
+ this object directly. Methods with the prefix
+ <code>remote_</code> will be callable on it. No state will be
+ transferred.</li>
+
+ <li><code>Viewable</code>: The remote side will refer to a
+ proxy for this object, which indicates what perspective
+ accessed this; as discussed above. Methods with the prefix
+ <code>view_</code> will be callable on it, and have an
+ additional first argument inserted (the perspective that
+ called the method). No state will be transferred.</li>
+
+ <li><code>Copyable</code>: Each time this object is
+ serialized, its state will be copied and sent. No methods are
+ remotely callable on it. By default, the state sent will be
+ the instance's <code>__dict__</code>, but a method
+ <code>getStateToCopyFor(perspective)</code> may be defined
+ which returns an arbitrary serializable object for
+ state.</li>
+
+ <li><code>Cacheable</code>: The first time this object is
+ serialized, its state will be copied and sent. Each
+ subsequent time, however, a reference to the original object
+ will be sent to the receiver. No methods will be remotely
+ callable on this object. By default, again, the state sent
+ will be the instance's <code>__dict__</code>but a method
+ <code>getStateToCacheAndObserveFor(perspective,
+ observer)</code> may be defined to return alternative state.
+ Since the state for this object is only sent once, the
+ <code>observer</code> argument is an object representative of
+ the receiver's representation of the <code>Cacheable</code>
+ after unserialization -- method calls to this object will be
+ resolved to methods prefixed with <code>observe_</code>,
+ <em>on the receiver's <code>RemoteCache</code> of this
+ object</em>. This may be used to keep the receiver's cache
+ up-to-date as relevant portions of the <code>Cacheable</code>
+ object change.</li>
+ </ul>
+
+ <h3>Publishing Objects with PB</h3>
+
+ <p>The previous samples of code have shown how an individual
+ object will interact over a previously-established PB
+ connection. In order to get to that connection, you need to do
+ some set-up work on both the client and server side; PB
+ attempts to minimize this effort.</p>
+
+ <p>There are two different approaches for setting up a PB
+ server, depending on your application's needs. In the simplest
+ case, where your application does not deal with the
+ abstractions above -- services, identities, and perspectives --
+ you can simply publish an object on a particular port.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class Echoer(pb.Root):
+ def remote_echo(self, st):
+ print 'echoing:', st
+ return st
+if __name__ == '__main__':
+ app = main.Application("pbsimple")
+ app.listenOn(8789, pb.BrokerFactory(Echoer()))
+ app.run()
+</pre>
+
+ <h6>Listing 11: Creating a simple PB server</h6>
+ </blockquote>
+
+ <p>Listing 11 shows how to publish a simple object which
+ responds to a single message, "echo", and returns whatever
+ argument is sent to it. There is very little to explain: the
+ "Echoer" class is a pb.Root, which is a small subclass of
+ Referenceable designed to be used for objects published by a
+ BrokerFactory, so Echoer follows the same rule for remote
+ access that Referenceable does. Connecting to this service is
+ almost equally simple.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def gotObject(object):
+ print "got object:",object
+ object.echo("hello network", pbcallback=gotEcho)
+def gotEcho(echo):
+ print 'server echoed:',echo
+ main.shutDown()
+def gotNoObject(reason):
+ print "no object:",reason
+ main.shutDown()
+pb.getObjectAt("localhost", 8789, gotObject, gotNoObject, 30)
+main.run()
+</pre>
+
+ <h6>Listing 12: A client for Echoer objects.</h6>
+ </blockquote>
+
+ <p>The utility function <code>pb.getObjectAt</code> retrieves
+ the root object from a hostname/port-number pair and makes a
+ callback (in this case, <code>gotObject</code>) if it can
+ connect and retrieve the object reference successfully, and an
+ error callback (<code>gotNoObject</code>) if it cannot connect
+ or the connection times out.</p>
+
+ <p><code>gotObject</code> receives the remote reference, and
+ sends the <code>echo</code> message to it. This call is
+ visually noticeable as a remote method invocation by the
+ distinctive <code>pbcallback</code> keyword argument. When the
+ result from that call is received, <code>gotEcho</code> will be
+ called, notifying us that in fact, the server echoed our input
+ ("hello network").</p>
+
+ <p>While this setup might be useful for certain simple types of
+ applications where there is no notion of a "user", the
+ additional complexity necessary for authentication and service
+ segregation is worth it. In particular, re-use of server code
+ for things like chat (twisted.words) is a lot easier with a
+ unified notion of users and authentication.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class SimplePerspective(pb.Perspective):
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+class SimpleService(pb.Service):
+ def getPerspectiveNamed(self, name):
+ return SimplePerspective(name, self)
+if __name__ == '__main__':
+ import pbecho
+ app = main.Application("pbecho")
+ pbecho.SimpleService("pbecho",app).getPerspectiveNamed("guest")\
+ .makeIdentity("guest")
+ app.listenOn(pb.portno, pb.BrokerFactory(pb.AuthRoot(app)))
+ app.save("start")
+</pre>
+
+ <h6>Listing 13: A PB server using twisted's "passport"
+ authentication.</h6>
+ </blockquote>
+
+ <p>In terms of the "functionality" it offers, this server is
+ identical. It provides a method which will echo some simple
+ object sent to it. However, this server provides it in a manner
+ which will allow it to cooperate with multiple other
+ authenticated services running on the same connection, because
+ it uses the central Authorizer for the application.</p>
+
+ <p>On the line that creates the <code>SimpleService</code>,
+ several things happen.</p>
+
+ <ol>
+ <li>A SimpleService is created and persistently added to the
+ <code>Application</code> instance.</li>
+
+ <li>A SimplePerspective is created, via the overridden
+ <code>getPerspectiveNamed</code> method.</li>
+
+ <li>That <code>SimplePerspective</code> has an
+ <code>Identity</code> generated for it, and persistently
+ added to the <code>Application</code>'s
+ <code>Authorizer</code>. The created identity will have the
+ same name as the perspective ("guest"), and the password
+ supplied (also, "guest"). It will also have a reference to
+ the service "pbecho" and a perspective named "guest", by
+ name. The <code>Perspective.makeIdentity</code> utility
+ method prevents having to deal with the intricacies of the
+ passport <code>Authorizer</code> system when one doesn't
+ require strongly separate <code>Identity</code>s and
+ <code>Perspective</code>s.</li>
+ </ol>
+ <br />
+ <br />
+
+
+ <p>Also, this server does not run itself, but instead persists
+ to a file which can be run with twistd, offering all the usual
+ amenities of daemonization, logging, etc. Once the server is
+ run, connecting to it is similar to the previous example.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def success(message):
+ print "Message received:",message
+ main.shutDown()
+def failure(error):
+ print "Failure...",error
+ main.shutDown()
+def connected(perspective):
+ perspective.echo("hello world",
+ pbcallback=success,
+ pberrback=failure)
+ print "connected."
+pb.connect(connected, failure, "localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 30)
+main.run()
+</pre>
+
+ <h6>Listing 14: Connecting to an Authorized Service</h6>
+ </blockquote>
+ <br />
+ <br />
+
+
+ <p>This introduces a new utility -- <code>pb.connect</code>.
+ This function takes a long list of arguments and manages the
+ handshaking and challenge/response aspects of connecting to a
+ PB service perspective, eventually calling back to indicate
+ either success or failure. In this particular example, we are
+ connecting to localhost on the default PB port (8787),
+ authenticating to the identity "guest" with the password
+ "guest", requesting the perspective "guest" from the service
+ "pbecho". If this can't be done within 30 seconds, the
+ connection will abort.</p>
+
+ <p>In these examples, I've attempted to show how Twisted makes
+ event-based scripting easier; this facilitates the ability to
+ run short scripts as part of a long-running process. However,
+ event-based programming is not natural to procedural scripts;
+ it is more generally accepted that GUI programs will be
+ event-driven whereas scripts will be blocking. An alternative
+ client to our <code>SimpleService</code> using GTK illustrates
+ the seamless meshing of Twisted and GTK.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import main, ingtkernet
+from twisted.spread.ui import gtkutil
+import gtk
+ingtkernet.install()
+class EchoClient:
+ def __init__(self, echoer):
+ l.hide()
+ self.echoer = echoer
+ w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.GtkVBox(); b = gtk.GtkButton("Echo:")
+ self.entry = gtk.GtkEntry(); self.outry = gtk.GtkEntry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', gtk.mainquit)
+ w.show_all()
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.echo(txt, pbcallback=self.outry.set_text)
+l = gtkutil.Login(EchoClient, None, initialService="pbecho")
+l.show_all()
+gtk.mainloop()
+</pre>
+
+ <h6>Listing 15: A Twisted GUI application</h6>
+ </blockquote>
+
+ <h3>Event-Driven Web Object Publishing with Web.Widgets</h3>
+
+ <p>Although PB will be interesting to those people who wish to
+ write custom clients for their networked applications, many
+ prefer or require a web-based front end. Twisted's built-in web
+ server has been designed to accommodate this desire, and the
+ presentation framework that one would use to write such an
+ application is <code>twisted.web.widgets</code>. Web.Widgets
+ has been designed to work in an event-based manner, without
+ adding overhead to the designer or the developer's
+ work-flow.</p>
+
+ <p>Surprisingly, asynchronous web interfaces fit very well into
+ the normal uses of purpose-built web toolkits such as PHP. Any
+ experienced PHP, Zope, or WebWare developer will tell you that
+ <em>separation of presentation, content, and logic</em> is very
+ important. In practice, this results in a "header" block of
+ code which sets up various functions which are called
+ throughout the page, some of which load blocks of content to
+ display. While PHP does not enforce this, it is certainly
+ idiomatic. Zope enforces it to a limited degree, although it
+ still allows control structures and other programmatic elements
+ in the body of the content.</p>
+
+ <p>In Web.Widgets, strict enforcement of this principle
+ coincides very neatly with a "hands-free" event-based
+ integration, where much of the work of declaring callbacks is
+ implicit. A "Presentation" has a very simple structure for
+ evaluating Python expressions and giving them a context to
+ operate in. The "header" block which is common to many
+ templating systems becomes a class, which represents an
+ enumeration of events that the template may generate, each of
+ which may be responded to either immediately or latently.</p>
+
+ <p>For the sake of simplicity, as well as maintaining
+ compatibility for potential document formats other than HTML,
+ Presentation widgets do not attempt to parse their template as
+ HTML tags. The structure of the template is <code>"HTML Text
+ %%%%python_expression()%%%% more HTML Text"</code>. Every set
+ of 4 percent signs (%%%%) switches back and forth between
+ evaluation and printing.</p>
+
+ <p>No control structures are allowed in the template. This was
+ originally thought to be a potentially major inconvenience, but
+ with use of the Web.Widgets code to develop a few small sites,
+ it has seemed trivial to encapsulate any table-formatting code
+ within a method; especially since those methods can take string
+ arguments if there's a need to customize the table's
+ appearance.</p>
+
+ <p>The namespace for evaluating the template expressions is
+ obtained by scanning the class hierarchy for attributes, and
+ getting each of those attributes from the current instance.
+ This means that all methods will be bound methods, so
+ indicating "self" explicitly is not required. While it is
+ possible to override the method for creating namespaces, using
+ this default has the effect of associating all presentation
+ code for a particular widget in one class, along with its
+ template. If one is working with a non-programmer designer, and
+ the template is in an external file, it is always very clear to
+ the designer what functionality is available to them in any
+ given scope, because there is a list of available methods for
+ any given class.</p>
+
+ <p>A convenient event to register for would be a response from
+ the PB service that we just implemented. We can use the
+ <code>Deferred</code> class in order to indicate to the widgets
+ framework that certain work has to be done later. This is a
+ Twisted convention which one can currently use in PB as well as
+ webwidgets; any framework which needs the ability to defer a
+ return value until later should use this facility. Elements of
+ the page will be rendered from top to bottom as data becomes
+ available, so the page will not be blocked on rendering until
+ all deferred elements have been completed.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.python import defer
+from twisted.web import widgets
+class EchoDisplay(widgets.Presentation):
+ template = """&lt;H1&gt;Welcome to my widget, displaying %%%%echotext%%%%.&lt;/h1&gt;
+ &lt;p&gt;Here it is: %%%%getEchoPerspective()%%%%&lt;/p&gt;"""
+ echotext = 'hello web!'
+ def getEchoPerspective(self):
+ d = defer.Deferred()
+ pb.connect(d.callback, d.errback, "localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 1)
+ d.addCallbacks(self.makeListOf, self.formatTraceback)
+ return ['&lt;b&gt;',d,'&lt;/b&gt;']
+ def makeListOf(self, echoer):
+ d = defer.Deferred()
+ echoer.echo(self.echotext, pbcallback=d.callback, pberrback=d.errback)
+ d.addCallbacks(widgets.listify, self.formatTraceback)
+ return [d]
+if __name__ == "__main__":
+ from twisted.web import server
+ from twisted.internet import main
+ a = main.Application("pbweb")
+ gdgt = widgets.Gadget()
+ gdgt.widgets['index'] = EchoDisplay()
+ a.listenOn(8080, server.Site(gdgt))
+ a.run()
+</pre>
+
+ <h6>Listing 16: an event-based web widget.</h6>
+ </blockquote>
+
+ <p>Each time a Deferred is returned as part of the page, the
+ page will pause rendering until the deferred's
+ <code>callback</code> method is invoked. When that callback is
+ made, it is inserted at the point in the page where rendering
+ left off.</p>
+
+ <p>If necessary, there are options within web.widgets to allow
+ a widget to postpone or cease rendering of the entire page --
+ for example, it is possible to write a FileDownload widget,
+ which will override the rendering of the entire page and
+ replace it with a file download.</p>
+
+ <p>The final goal of web.widgets is to provide a framework
+ which encourages the development of usable library code. Too
+ much web-based code is thrown away due to its particular
+ environment requirements or stylistic preconceptions it carries
+ with it. The goal is to combine the fast-and-loose iterative
+ development cycle of PHP with the ease of installation and use
+ of Zope's "Product" plugins.</p>
+
+ <h3>Things That Twisted Does Not Do</h3>
+
+ <p>It is unfortunately well beyond the scope of this paper to
+ cover all the functionality that Twisted provides, but it
+ serves as a good overview. It may seem as though twisted does
+ anything and everything, but there are certain features we
+ never plan to implement because they are simply outside the
+ scope of the project.</p>
+
+ <p>Despite the multiple ways to publish and access objects,
+ Twisted does not have or support an interface definition
+ language. Some developers on the Twisted project have
+ experience with remote object interfaces that require explicit
+ specification of all datatypes during the design of an object's
+ interface. We feel that such interfaces are in the spirit of
+ statically-typed languages, and are therefore suited to the
+ domain of problems where statically-typed languages excel.
+ Twisted has no plans to implement a protocol schema or static
+ type-checking mechanism, as the efficiency gained by such an
+ approach would be quickly lost again by requiring the type
+ conversion between Python's dynamic types and the protocol's
+ static ones. Since one of the key advantages of Python is its
+ extremely flexible dynamic type system, we felt that a
+ dynamically typed approach to protocol design would share some
+ of those advantages.</p>
+
+ <p>Twisted does not assume that all data is stored in a
+ relational database, or even an efficient object database.
+ Currently, Twisted's configuration state is all stored in
+ memory at run-time, and the persistent parts of it are pickled
+ at one go. There are no plans to move the configuration objects
+ into a "real" database, as we feel it is easier to keep a naive
+ form of persistence for the default case and let
+ application-specific persistence mechanisms handle persistence.
+ Consequently, there is no object-relational mapping in Twisted;
+ <code>twisted.enterprise</code> is an interface to the
+ relational paradigm, not an object-oriented layer over it.</p>
+
+ <p>There are other things that Twisted will not do as well, but
+ these have been frequently discussed as possibilities for it.
+ The general rule of thumb is that if something will increase
+ the required installation overhead, then Twisted will probably
+ not do it. Optional additions that enhance integration with
+ external systems are always welcome: for example, database
+ drivers for Twisted or a CORBA IDL for PB objects.</p>
+
+ <h3>Future Directions</h3>
+
+ <p>Twisted is still a work in progress. The number of protocols
+ in the world is infinite for all practical purposes, and it
+ would be nice to have a central repository of event-based
+ protocol implementations. Better integration with frameworks
+ and operating systems is also a goal. Examples for integration
+ opportunities are automatic creation of installer for "tap"
+ files (for Red Hat Packager-based distributions, FreeBSD's
+ package management system or Microsoft Windows(tm) installers),
+ and integration with other event-dispatch mechanisms, such as
+ win32's native message dispatch.</p>
+
+ <p>A still-nascent feature of Twisted, which this paper only
+ touches briefly upon, is <code>twisted.enterprise</code>: it is
+ planned that Twisted will have first-class database support
+ some time in the near future. In particular, integration
+ between twisted.web and twisted.enterprise to allow developers
+ to have SQL conveniences that they are used to from other
+ frameworks.</p>
+
+ <p>Another direction that we hope Twisted will progress in is
+ standardization and porting of PB as a messaging protocol. Some
+ progress has already been made in that direction, with XEmacs
+ integration nearly ready for release as of this writing.</p>
+
+ <p>Tighter integration of protocols is also a future goal, such
+ an FTP server that can serve the same resources as a web
+ server, or a web server that allows users to change their POP3
+ password. While Twisted is already a very tightly integrated
+ framework, there is always room for more integration. Of
+ course, all this should be done in a flexible way, so the
+ end-user will choose which components to use -- and have those
+ components work well together.</p>
+
+ <h3>Conclusions</h3>
+
+ <p>As shown, Twisted provides a lot of functionality to the
+ Python network programmer, while trying to be in his way as
+ little as possible. Twisted gives good tools for both someone
+ trying to implement a new protocol, or someone trying to use an
+ existing protocol. Twisted allows developers to prototype and
+ develop object communication models with PB, without designing
+ a byte-level protocol. Twisted tries to have an easy way to
+ record useful deployment options, via the
+ <code>twisted.tap</code> and plugin mechanisms, while making it
+ easy to generate new forms of deployment. And last but not
+ least, even Twisted is written in a high-level language and
+ uses its dynamic facilities to give an easy API, it has
+ performance which is good enough for most situations -- for
+ example, the web server can easily saturate a T1 line serving
+ dynamic requests on low-end machines.</p>
+
+ <p>While still an active project, Twisted can already used for
+ production programs. Twisted can be downloaded from the main
+ Twisted site (http://www.twistedmatrix.com) where there is also
+ documentation for using and programming Twisted.</p>
+
+ <h3>Acknowledgements</h3>
+
+ <p>We wish to thank Sean Riley, Allen Short, Chris Armstrong,
+ Paul Swartz, J&uuml;rgen Hermann, Benjamin Bruheim, Travis B.
+ Hartwell, and Itamar Shtull-Trauring for being a part of the
+ Twisted development team with us.</p>
+
+ <p>Thanks also to Jason Asbahr, Tommi Virtanen, Gavin Cooper,
+ Erno Kuusela, Nick Moffit, Jeremy Fincher, Jerry Hebert, Keith
+ Zaback, Matthew Walker, and Dan Moniz, for providing insight,
+ commentary, bandwidth, crazy ideas, and bug-fixes (in no
+ particular order) to the Twisted team.</p>
+
+ <h3>References</h3>
+
+ <ol>
+ <li>The Twisted site, http://www.twistedmatrix.com</li>
+
+ <li>Douglas Schmidt, Michael Stal, Hans Rohnert and Frank
+ Buschmann, Pattern-Oriented Software Architecture, Volume 2,
+ Patterns for Concurrent and Networked Objects, John Wiley
+ &amp; Sons</li>
+
+ <li>Abhishek Chandra, David Mosberger, Scalability of Linux
+ Event-Dispatch Mechanisms, USENIX 2001,
+ http://lass.cs.umass.edu/~abhishek/papers/usenix01/paper.ps</li>
+
+ <li>Protocol specifications, http://www.rfc-editor.com</li>
+
+ <li>The Twisted Philosophical FAQ,
+ http://www.twistedmatrix.com/page.epy/twistedphil.html</li>
+
+ <li>Twisted Advocacy,
+ http://www.twistedmatrix.com/page.epy/whytwisted.html</li>
+
+ <li>Medusa, http://www.nightmare.com/medusa/index.html</li>
+
+ <li>Using Spreadable Web Servers,
+ http://www.twistedmatrix.com/users/jh.twistd/python/moin.cgi/TwistedWeb</li>
+
+ <li>Twisted Spread implementations for other languages,
+ http://www.twistedmatrix.com/users/washort/</li>
+
+ <li>PHP: Hypertext Preprocessor, http://www.php.net/</li>
+
+ <li>The Z Object Publishing Environment,
+ http://www.zope.org/, http://zope.com/</li>
+ </ol>
+ </body>
+</html>
+
diff --git a/doc/historic/2003/europython/doanddont.html b/doc/historic/2003/europython/doanddont.html
new file mode 100644
index 0000000..79b0072
--- /dev/null
+++ b/doc/historic/2003/europython/doanddont.html
@@ -0,0 +1,508 @@
+<html><head><title>Idioms and Anti-Idioms in Python</title></head><body>
+
+<h1>Idioms and Anti-Idioms in Python</h1>
+
+<h2>Idioms and Anti-Idioms, AKA Do and Don't</h2><ul>
+<li>Welcome</li>
+
+<li>Gimmick -- Charmed quotes</li>
+
+</ul>
+<hr />
+<em>Prue (Something Wicca This Way Comes, season 1) -- No, we are not supposed to use our powers</em>
+<h2>Python</h2><ul>
+<li>Few gotchas...</li>
+
+<li>...but not zero</li>
+
+<li>Most are easy to avoid...</li>
+
+<li>...if you know about them.</li>
+
+</ul>
+<hr />
+<em>Prue (Something Wicca This Way Comes, season 1) -- Uh, it doesn't work out there either.</em>
+<h2>Exceptions</h2><ul>
+<li>Primary method of dealing with errors</li>
+
+<li>Flexible</li>
+
+<li>Good opportunity to shoot yourself in foot...</li>
+
+<li>...without knowing about it (bug only shows up rarely.)</li>
+
+</ul>
+<hr />
+<em>Leo (Paige From the Past, season 4) -- You have to [...] Paige. No exceptions.</em>
+<h2>Exceptions -- Catching Too Much</h2><ul>
+<li>Classical case: 'except:'</li>
+
+<li>Will catch anything</li>
+
+<li>Including most bugs...<ul><li>NameError, AttributeError...</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Piper (Charmed Again, season 4) -- Okay, well this is way too much for me to handle. </em>
+<h2>Exceptions -- Catching Too Much -- Example</h2>
+<pre class="python">
+try:
+ f = opne("file")
+except:
+ sys.exit("no such file")
+</pre>
+
+<hr />
+<em>Piper (Charmed Again, season 4) -- Way too much.</em>
+<h2>Exceptions -- Catching Too Soon</h2><ul>
+<li>The slogan:<ul><li>Don't catch errors you can do nothing about</li>
+</ul></li>
+
+<li>Catching exceptions should by the 'user'</li>
+
+<li>The point where the value is *used*<ul><li>Rather than passed on</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Phoebe (Knight to Remember, season 4) -- Maybe it's just too soon.</em>
+<h2>Exceptions -- Catching Too Soon -- Example</h2>
+<pre class="python">
+def readlinesfromfile(file):
+ try:
+ return open(file).readlines()
+ except IOError:
+ pass # do what?
+</pre>
+<ul><li>What can we do?<ul><li>Return empty list? bad</li>
+
+<li>Print warning? what if it's one of several possibilities</li>
+
+<li>Exit? NO!</li>
+
+<li>Raise our own exception? Losing information</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Paige (Knight to Remember, season 4) -- I'm already a little late.</em>
+<h2>Catching multiple exceptions</h2><ul>
+<li>Spot bug here:</li></ul>
+
+<pre class="python">
+try:
+ fp = open("file")
+except IOError, OSError:
+ print "could not open file"
+</pre>
+<hr />
+<em>Paige (Knight to Remember, season 4) -- Because I've got too many responsibilities</em>
+<h2>Catching multiple exceptions (cont'd)</h2><ul>
+<li>Bug:</li>
+
+<li>Only IOError gets caught...</li>
+
+<li>and exception value is put in OSError</li>
+
+<li>But most exceptions are IOError :(</li>
+
+<li>Likely to not discover this bug</li>
+
+</ul>
+<hr />
+<em>Piper (Knight to Remember, season 4) -- Alright! Calm down!</em>
+<h2>Catching multiple exceptions (cont'd 2)</h2><ul>
+<li>Correct way</li></ul>
+
+<pre class="python">
+try:
+ fp = open("file")
+except (IOError, OSError):
+ print "could not open file"
+</pre>
+<hr />
+<em>Phoebe (Knight to Remember, season 4) -- Besides that, maybe we can help</em>
+<h2>Catching NameError</h2>
+
+<pre class="python">
+try:
+ import foo
+except ImportError:
+ pass
+
+try:
+ foo.Function()
+except NameError:
+ pass # some replacement
+</pre>
+<ul><li>Bad idea!</li>
+
+</ul>
+<hr />
+<em>Piper (Morality Bites, season 4) -- That's OK, I forgot your name too.</em>
+<h2>Catching NameError (cont'd)</h2>
+
+<pre class="python">
+try:
+ import foo
+except ImportError:
+ foo = None
+
+if foo is not None:
+ foo.Function()
+else:
+ pass # some replacement
+</pre>
+<ul><li>If foo.Function() sometimes has a NameError, we won't mask it...</li>
+
+<li>...or if we misspell 'foo'</li>
+
+</ul>
+<hr />
+<em>Anne (Morality Bites, season 4) -- Oh, right, sorry.</em>
+<h2>Importing Modules -- A Review</h2><ul>
+<li>'import module'</li>
+
+<li>'from module import name1, name2'</li>
+
+<li>Only imports once (or does it?)</li>
+
+</ul>
+<hr />
+<em>Phoebe (Animal Pragmatism, season 2) -- Rome was not built in a day,</em>
+<h2>Importing __main__</h2><ul>
+<li>__main__ is where the 'script' is executed</li>
+
+<li>Avoid the temptation to import __main__</li>
+
+<li>Put common function in a named module</li>
+
+<li>Then your code will be more useful</li>
+
+</ul>
+<hr />
+<em>Piper (Animal Pragmatism, season 2) -- And why mess with a good thing?</em>
+<h2>Importing a File Twice</h2><ul>
+<li>But it can't be, can it?</li>
+
+<li>Importing a script into itself</li></ul>
+
+<pre class="python">
+# file: hello.py
+import hello
+class Foo: pass
+</pre>
+<ul><li>Two 'Foo's, same definition, different class!</li>
+
+<li>If your sys.path includes packages...</li>
+
+<li>...you can import a module once from a package and once plain</li>
+
+</ul>
+<hr />
+<em>Phoebe (Which Prue Is It, Anyway?, season 1) -- Okay, which one of you is the real Prue?</em>
+<h2>Importing *</h2><ul>
+<li>Don't do it<ul><li>Don't do it</li>
+</ul></li>
+
+<li>Classic mistake:</li></ul>
+
+<pre class="python">
+from os import *
+
+fp = open("file") # works
+fp.readline() # fails with a weird error...?
+</pre>
+<ul><li>os.open returns a file descriptor (number)</li>
+
+</ul>
+<hr />
+<em>Pink Prue (Which Prue Is It, Anyway?, season 1) -- So, um, what did I do now?</em>
+<h2>Importing * Inside Functions</h2><ul>
+<li>Just invalid Python...</li>
+
+<li>...but happens to work in 1.5.2...</li>
+
+<li>...and sometimes in 2.1...</li>
+
+<li>...never in 2.2.</li>
+
+<li>Just Say No</li>
+
+</ul>
+<hr />
+<em>Pink Prue (Which Prue Is It, Anyway?, season 1) -- What ever it is, I have an alibi.</em>
+<h2>Importing Names</h2><ul>
+<li>from foo import name1, name2</li>
+
+<li>Not a bad idea always</li>
+
+<li>But be careful of repercussions:</li>
+
+<li>modules sometimes change things inside</li>
+
+<li>You won't see those changes</li>
+
+<li>Opportunity for inconsistency!</li>
+
+</ul>
+<hr />
+<em>Real Prue (Which Prue Is It, Anyway?, season 1) -- Because I still have to work here when all of this is over.</em>
+<h2>Reloading</h2><ul>
+<li>reload(module) -- reread module from file</li>
+
+<li>Useful in long running processes?</li>
+
+<li>Doesn't play nice with 'from import name'</li>
+
+<li>Beware of exceptions: the new classes are different from old classes</li>
+
+</ul>
+<hr />
+<em>Real Prue (Which Prue Is It, Anyway?, season 1) -- Don't worry I'm never casting that spell again.</em>
+<h2>exec, execfile and eval</h2><ul>
+<li>Execute arbitrary Python code</li>
+
+<li>No-cost scripting language for applications</li>
+
+<li>But easy to shoot one's self in the foot</li>
+
+</ul>
+<hr />
+<em>Reporter (Morality Bites, season 2) -- More news on the execution of Phoebe Halliwell coming up.</em>
+<h2>exec, execfile and eval -- Modify namespaces</h2><ul>
+<li>They modify the namespace they're in<ul><li>...but not always.</li>
+</ul></li>
+
+<li>Depends on global vs. inside functions</li>
+
+<li>Use with care -- or with explicit dictionaries</li>
+
+</ul>
+<hr />
+<em>Nathaniel (Morality Bites, season 2) -- Executions are a bitch to plan.</em>
+<h2>exec, execfile and eval -- Inside functions</h2><ul>
+<li>Unadorned exec is invalid inside functions</li>
+
+<li>execfile and eval play badly with local var. optimisation</li>
+
+<li>Always use with explicit dictionary</li>
+
+</ul>
+<hr />
+<em>Nathaniel (Morality Bites, season 2) -- Phoebe, what is this? An attempt to stay your execution?</em>
+<h2>Conclusion: recommended usage</h2><ul>
+<li>d={};exec "code" in d</li>
+
+<li>d={};execfile("file", d)</li>
+
+<li>d={};eval("expression", d)</li>
+
+<li>Sometimes useful to pre-populate dictionary</li>
+
+</ul>
+<hr />
+<em>Phoebe (Morality Bites, season 2) -- Just because you don't understand something, doesn't make it evil.</em>
+<h2>exec, execfile and eval -- Restricted Execution (Don't)</h2><ul>
+<li>rexec never was audited</li>
+
+<li>History of holes</li>
+
+<li>Dangerous to allow arbitrary code</li>
+
+<li>DoS attacks not defended against at all</li>
+
+<li>Recursion</li>
+
+</ul>
+<hr />
+<em>Leo (Morality Bites, season 2) -- Nobody's gonna rescue you.</em>
+<h2>Syntax</h2><ul>
+<li>Python syntax regular and nice...</li>
+
+<li>...but not perfect.</li>
+
+<li>Some care needed.</li>
+
+</ul>
+<hr />
+<em>Prue (Morality Bites, season 2) -- You know, we can still make the good things happen.</em>
+<h2>Syntax -- Tabs and Spaces</h2><ul>
+<li>Use Tabs</li>
+
+<li>Or use spaces</li>
+
+<li>But don't mix them...</li>
+
+<li>...ever!</li>
+
+<li>Invites bugs</li>
+
+</ul>
+<hr />
+<em>Prue (The Painted World, season 2) -- We've seen so many bizarre things.</em>
+<h2>Syntax -- Backslash Continuations</h2>
+
+<pre class="python">
+# Extra newline
+r = 1 \
+
++2
+
+# Missing backslash in long series
+r = 1 \
++2 \
++3 \
++4
++5 \
++6
+</pre>
+<ul><li>Both *silently* do the wrong things</li></ul>
+
+<h2>Syntax -- Backslash Continuations (cont'd)</h2>
+
+<ul><li>Better</li></ul>
+
+<pre class="python">
+# Extra newline
+r = (1
+
++2)
+
+# Long series
+r = (1
++2
++3
++4
++5
++6)
+</pre>
+
+<hr />
+<em>Prue (The Painted World, season 2) -- Uh, what just happened here?</em>
+<h2>Hand Hacking Batteries</h2><ul>
+<li>Don't write os.path functions yourself<ul><li>os.path.join especially</li>
+</ul></li>
+
+<li>min, max</li>
+
+<li>urlparse</li>
+
+<li>Skim through modules list. A lot.</li>
+
+</ul>
+<hr />
+<em>Prue (Animal Pragmatism, season 2) -- Well, we didn't find anything in the Book Of Shadows.</em>
+<h2>Further Reading</h2><ul>
+<li><a href="http://aspn.activestate.com/ASPN/Python/Reference/Products/ActivePython/howtos/doanddont/doanddont.html">http://aspn.activestate.com/ASPN/Python/Reference/Products/ActivePython/howtos/doanddont/doanddont.html</a></li>
+
+<li><a href="http://www.amk.ca/python/writing/warts.html">http://www.amk.ca/python/writing/warts.html</a></li>
+
+<li><a href="http://www.python.org/doc/essays/styleguide.html">http://www.python.org/doc/essays/styleguide.html</a></li>
+
+</ul>
+<hr />
+<em>Phoebe (The Painted World, season 2) -- I think you'll find me pretty knowledgeable about all areas</em>
+<h2>Questions?</h2>
+<em>Piper (The Painted World, season 2) -- You're like ask rainman.com</em>
+
+<h2>Bonus Slides</h2>
+<em>Phoebe (The Painted World, season 2) -- Oh, and P.S. there will be no personal gain.</em>
+
+<h2>Packages and __init__.py</h2><ul>
+<li>Packages are determined by __init__.py files</li>
+
+<li>Temptation to put code in __init__.py</li>
+
+<li>But two namespaces mix: __init__'s and filesystem's</li>
+
+<li>Put comments, docstring and __all__</li>
+
+</ul>
+<hr />
+<em>Piper (Animal Pragmatism, season 2) -- It's a package. One I would like to share with you.</em>
+<h2>Type Checking</h2><ul>
+<li>Python's typing is highly dynamic</li>
+
+<li>Capability-based, not class-based</li>
+
+<li>Explicit type checks hurt code usefulness</li>
+
+<li>(common use -- proxies, for testing)</li>
+
+</ul>
+<hr />
+<em>Phoebe (Black as Cole, season 2) -- I never thought of myself as the marrying type</em>
+<h2>Type Checking -- Example</h2><ul>
+<li>Here's what not to do:</li></ul>
+
+<pre class="python">
+class Foo:
+ def __init__(self, i):
+ if type(i) is types.StringType:
+ self.content = open(i).readlines()
+ elif type(i) is types.ListType:
+ self.content = i
+</pre>
+<ul><li>(inspired from a question on #python)</li>
+
+<li>More badness than you can shake a stick at.</li>
+
+</ul>
+<hr />
+<em>Phoebe (Muse to My Ears, season 4) -- You're an artistic, creative type.</em>
+<h2>Type Checking -- Example -- Fixed</h2>
+<pre class="python">
+
+class Foo:
+ pass
+
+class FooFromFile(Foo):
+
+ def __init__(self, filename):
+ self.content = open(filename).readlines()
+
+class FooFromList(Foo):
+
+ def __init__(self, list):
+ self.content = list
+</pre>
+
+<hr />
+<em>Phoebe (Muse to My Ears, season 4) -- You see how well this worked out?</em>
+<h2>Private __Attributes</h2><ul>
+<li>Useful in deep hierarchies to keep attributes separate</li>
+
+<li>Mangle only class name -- *not* module name</li>
+
+<li>Makes it harder to test</li>
+
+<li>Makes it harder to hand-hack for debugging</li>
+
+</ul>
+<hr />
+<em>Tessa (Animal Pragmatism, season 2) -- Maybe it's our fault because we tried to make them into something they're not.</em>
+
+<h2>Using Mutable Default Arguments</h2>
+
+<pre class="python">
+def foo(l=[]):
+ l.append(5);return l
+</pre>
+
+<ul>
+<li>Will modify the same list.</li>
+<li>If you want that -- use object, class, not that hack.</li>
+</ul>
+
+<pre class="python">
+def foo(l=None):
+ if l is None: l=[]
+ l.append(5);return l
+</pre>
+<hr />
+<em>Snake guy (Animal Pragmatism, season 2) --
+You two are acting like nothing's changed.</em>
+
+</body></html>
diff --git a/doc/historic/2003/europython/index.html b/doc/historic/2003/europython/index.html
new file mode 100644
index 0000000..051fb6d
--- /dev/null
+++ b/doc/historic/2003/europython/index.html
@@ -0,0 +1,35 @@
+<html><head><title>Moshe's Talks</title></head><body>
+
+<h1>Moshe's talks</h1>
+
+<h2>Slides</h2>
+
+<ul>
+<li><a href="webclients-1.html">Writing Web Clients</a></li>
+<li><a href="doanddont-1.html">Idioms and Anti-Idioms</a></li>
+<li><a href="twisted-1.html">Introduction to Twisted</a></li>
+<li><a href="tw-deploy-1.html">Configuring and Deploying Twisted Web</a></li>
+<li><a href="lore-1.html">Using Lore</a></li>
+</ul>
+
+<h2>HTML</h2>
+
+<ul>
+<li><a href="webclients.html">Writing Web Clients</a></li>
+<li><a href="doanddont.html">Idioms and Anti-Idioms</a></li>
+<li><a href="twisted.html">Introduction to Twisted</a></li>
+<li><a href="tw-deploy.html">Configuring and Deploying Twisted Web</a></li>
+<li><a href="lore.html">Using Lore</a></li>
+</ul>
+
+<h2>PDF</h2>
+
+<ul>
+<li><a href="webclients.pdf">Writing Web Clients</a></li>
+<li><a href="doanddont.pdf">Idioms and Anti-Idioms</a></li>
+<li><a href="twisted.pdf">Introduction to Twisted</a></li>
+<li><a href="tw-deploy.pdf">Configuring and Deploying Twisted Web</a></li>
+<li><a href="lore.pdf">Using Lore</a></li>
+</ul>
+
+</body></html>
diff --git a/doc/historic/2003/europython/lore.html b/doc/historic/2003/europython/lore.html
new file mode 100644
index 0000000..edb33cc
--- /dev/null
+++ b/doc/historic/2003/europython/lore.html
@@ -0,0 +1,502 @@
+<html><head><title>Lore</title></head><body>
+
+<h1>Lore</h1>
+<h2>Lore - A Document Generation System</h2><ul>
+<li>Gimmick -- Gilmore girls quotes</li>
+
+<li>Goal - take something which is easy to write, transforms to something easy to read</li>
+
+<li>For correct definitions of 'easy', of course</li>
+
+</ul>
+<hr />
+<em>Rory (Concert Interruptus, season 1) -- Yeah, well I've always thought easy is completely overrated.</em>
+<h2>Source Format</h2><ul>
+<li>Subset of XHTML 1.0<ul><li>Except for some new attributes</li>
+
+<li>Shouldn't bother browsers</li>
+</ul></li>
+
+<li>Slanted towards logical markup</li>
+
+</ul>
+<hr />
+<em>Alex (I Solemnly Swear, season 3) -- That would've been far too logical.</em>
+
+<h2>Output Formats</h2><ul>
+<li>Screen and paper<ul><li>Screen - 'fancy HTML'</li>
+
+<li>Paper - LaTeX<ul><li>Use LaTeX to produce PDF or PostScript</li>
+</ul></li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Madelaine (The Lorelais' First Day at Chilton, season 1) -- You don't know she's going out for the paper.</em>
+<h2>Minimal Lore Document</h2>
+<pre>
+&lt;html&gt;
+&lt;head&gt;&lt;title&gt;Title&lt;/title&gt;&lt;/head&gt;
+&lt;body&gt;&lt;h1&gt;Title&lt;/h1&gt;&lt;/body&gt;
+&lt;/html&gt;</pre>
+
+<hr />
+<em>Luke (There's the Rub, season 2) -- You said minimal</em>
+<h2>Minimal Lore Document Explained</h2><ul>
+<li>title element in head -- a must</li>
+
+<li>h1 element in head -- a must<ul><li>Must have same content</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Tom (There's the Rub, season 2) -- Hey, this is minimal</em>
+<h2>External Listings</h2><ul>
+<li>Advantage -- no need to quote</li>
+
+<li>Advantage -- test your examples</li>
+
+<li>Example:<ul><li><code>&lt;a class="python-listing" href="/usr/lib/python2.2/os.py"&gt;os.py&lt;/a&gt;</code>
+</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Kirk (Red Light on the Wedding Night, season 2) -- I include it as an example of the excellence I aspire to.</em>
+<h2>Using Lore to Generate HTML</h2><ul>
+<li>Write template</li>
+
+<li>[optional] Write stylesheet</li>
+
+<li>Run lore</li>
+
+</ul>
+<hr />
+<em>Paris (Run Away, Little Boy, season 2) -- I went on the web and found this site</em>
+<h2>Generating LaTeX</h2><ul>
+<li>lore -olatex file.html --&gt; produces file.tex</li>
+
+<li>Default is to create an 'article'</li>
+
+<li>Creating PostScript<ul><li>latex file.tex</li>
+
+<li>latex file.tex</li>
+
+<li>dvips -o file.ps file.dvi</li>
+</ul></li>
+
+<li>Creating PDF<li>latex file.tex</li>
+<li>pdflatex file.tex</li>
+</li>
+
+</ul>
+<hr />
+<em>Rory (Christopher Returns, season 1) -- He had already printed like a million</em>
+<h2>Using Lint</h2><ul>
+<li>lore -olint doc/howto/*.html</li>
+
+<li>lore -n -olint doc/howto/*.html #no output except warnings</li>
+
+</ul>
+<hr />
+<em>Max (The Deer-Hunters, season 1) -- I know a D seems pretty dismal</em>
+<h2>Further Reading</h2><ul>
+<li>Man page -- doc/man/lore.xhtml</li>
+
+<li>Howto -- doc/howto/lore.xhtml</li>
+
+<li>Extending howto -- doc/howto/extending-lore.xhtml</li>
+
+<li>Documentation standard -- doc/howto/doc-standard.xhtml</li>
+
+<li>Lore paper -- doc/historic/2003/pycon/lore/lore.html</li>
+
+</ul>
+<hr />
+<em>Paris (The Bracebridge Dinner, season 2) -- Rereading the Iliad a third time is not not doing anything</em>
+<h2>Questions?</h2>
+<em>Lorelai (Forgiveness and Stuff, season 1) -- A person needs details.</em>
+<h2>Bonus Slides</h2>
+<em>Miss James (The Lorelais' First Day at Chilton, season 1) -- If you do it in Latin you get extra credit.</em>
+<h2>Lore Alternatives - LaTeX</h2><ul>
+<li>Very good at printed results</li>
+
+<li>Model makes alternative parsers near-impossible</li>
+
+<li>Renderers to HTML are buggy and fragile</li>
+
+<li>People find it hard to use</li>
+
+</ul>
+<hr />
+<em>Michel (Love, Daisies and Troubadors, season 1) -- It increases my ennui</em>
+<h2>Lore Alternatives - HTML</h2><ul>
+<li>Too flexible</li>
+
+<li>No support for needed idioms<ul><li>Special-purpose Python markup</li>
+
+<li>Tables of contents</li>
+
+<li>Inlining</li>
+
+<li>Footnotes</li>
+</ul></li>
+
+<li>Renders badly to dead trees with current tools</li>
+
+</ul>
+<hr />
+<em>Lorelai (Love, Daisies and Troubadors, season 1) -- It was broken [...] I'm not crazy</em>
+<h2>Lore Alternatives - Docbook</h2><ul>
+<li>Using correctly requires too much work<ul><li>Write a DTD with special elements</li>
+
+<li>Write Jade stylesheets</li>
+</ul></li>
+
+<li>Lore is probably smaller than docbook specialisation</li>
+
+<li>People find it hard to use</li>
+
+</ul>
+<hr />
+<em>Rory (Hammers and Veils, season 2) -- What do you want me to do it?</em>
+<h2>Lore Alternatives - Texinfo</h2><ul>
+<li>Next slide, please</li>
+
+</ul>
+<hr />
+<em>Man (Hammers and Veils, season 2) -- There's a ton of hurt that almost happened here.</em>
+<h2>Lore Alternatives - reST</h2><ul>
+<li>Completely new language (no editor support)</li>
+
+<li>Hard to add new tags</li>
+
+<li>No linter</li>
+
+</ul>
+<hr />
+<em>Emily (Hammers and Veils, season 2) -- And this is what we need to discuss right now?</em>
+<h2>Lore Alternatives - LyX</h2><ul>
+<li>Dependent on GUI</li>
+
+</ul>
+<hr />
+<em>Rory (Hammers and Veils, season 2) -- Well, it's just dressed up a little.</em>
+<h2>Some Standard Tags -- XHTML Primer</h2><ul>
+<li><code>&lt;p&gt;paragraph&lt;/p&gt;</code>
+</li>
+
+<li><code>&lt;em&gt;emphasis&lt;/em&gt;</code>
+</li>
+
+<li><code>&lt;strong&gt;strong emphasis&lt;/strong&gt;</code>
+</li>
+
+<li>Headers<ul><li>&lt;h2&gt;sectionheader&lt;/h2&gt;</li><li>&lt;h3&gt;subsection&lt;/h3&gt;</li></ul></li>
+
+<li>Lists<ul><li>&lt;ol&gt;&lt;li&gt;ordered list item&lt;/li&gt;&lt;/ol&gt;</li><li>&lt;ul&gt;&lt;li&gt;unordered list item&lt;/li&gt;&lt;/ul&gt;</li></ul></li>
+
+<li><code>&lt;img src="http://example.com/img.png" /&gt;</code>
+<ul><li>Note '/' at end!</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Rory (Kiss and Tell, season 1) -- See, even a little information in your hands is dangerous.</em>
+<h2>More HTML</h2><ul>
+<li>Indicating authorship -- &lt;link rel="author" href="author@example.com" title="Author Name" /&gt;</li>
+
+<li>Put in &lt;head&gt;</li>
+
+<li>sub/sup -- subscripts, superscripts</li>
+
+</ul>
+<hr />
+<em>Max (The Lorelais' First Day at Chilton, season 1) -- Tolstoy's favourite author, for instance, was...</em>
+<h2>More HTML -- cross references</h2><ul>
+<li>Label<ul><code>&lt;a name="label-name" /&gt;</code>
+</ul></li>
+
+<li>Reference in file<ul><li><code>&lt;a href="#label-name"&gt;reference text&lt;/a&gt;</code></li>
+</ul></li>
+
+<li>Reference in other file<ul><li><code>&lt;a href="file-name#label-name"&gt;reference text&lt;/a&gt;</code></li>
+</ul></li>
+
+<li>Refer to URL<ul><li><code>&lt;a href="http://example.com"&gt;reference text&lt;/a&gt;</code></li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Christopher (Christopher Returns, season 1) -- It's just a weird reference.</em>
+<h2>Special Markup</h2><ul>
+<li>Things not in XHTML are done with div/span classes</li>
+
+<li>&lt;div class="note"&gt;note&lt;/a&gt; -- notes</li>
+
+<li>&lt;div class="doit"&gt;doit&lt;/a&gt; -- something not implemented</li>
+
+<li>&lt;span class="footnote"&gt;footnote&lt;/a&gt; -- put in a footnote</li>
+
+</ul>
+<hr />
+<em>Taylor (Take The Deviled Eggs, season 3) -- Out attention spans are gnat-like tonight</em>
+<h2>API References</h2><ul>
+<li><code>&lt;code class="API"&gt;urllib&lt;/code&gt;</code>
+</li>
+
+<li><code>&lt;code base="urllib" class="API"&gt;urlencode&lt;/code&gt;</code>
+</li>
+
+<li><code>&lt;code base="twisted" class="API"&gt;copyright.version&lt;/code&gt;</code>
+</li>
+
+</ul>
+<hr />
+<em>Lorelai (The Road Trip To Harvard, season 2) -- We're just kinda hanging out between classes</em>
+<h2>API References Explained</h2><ul>
+<li>Integrate with systems for docstring generation</li>
+
+<li>Generate links to auto-generated docs</li>
+
+</ul>
+<hr />
+<em>Luke (Love and War and Snow, season 1) -- How do you know? Do you have written documentation?</em>
+<h2>Inline Listings</h2><ul>
+<li>Use &lt;pre&gt;</li>
+
+<li>Possible classes: python, shell, python-interpreter</li>
+
+<li>Example:</li>
+
+</ul>
+<pre>
+&lt;pre class="python"&gt;
+def foo():
+ return forbnicate(4)
+&lt;/pre&gt;</pre>
+<hr />
+<em>Taylor (Take The Deviled Eggs, season 3) -- That's not even English.</em>
+<h2>Inline Listings -- short</h2><ul>
+<li>Use &lt;code&gt;</li>
+
+<li>Possible classes: python, shell, py-signature</li>
+
+<li>...and more</li>
+
+</ul>
+<hr />
+<em>Rory (Double Date, season 1) -- It's like this weird code thing with her.</em>
+<h2>Generating HTML -- writing templates</h2><ul>
+<li>Templates are XHTML documents</li>
+
+<li>Put in reference to stylesheet -- head is mostly kept as is</li>
+
+<li>Title will be prepended to document's title</li>
+
+<li>&lt;div class="toc" /&gt; will be replaced by table of contents</li>
+
+<li>&lt;div class="body" /&gt; will be replaced by processed body</li>
+
+</ul>
+<hr />
+<em>Paris (I Can't Get Started, season 2) -- How's this sound for a template?</em>
+<h2>Generating HTML -- using commandline</h2><ul>
+<li>Full details: the lore manpage</li>
+
+<li>Basic format: lore file.html --&gt; outputs file.xhtml<ul><li>--config template=template.tpl to use different template</li>
+
+<li>--config baseurl=format-string for the url of the auto-generated docstring docs</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Richard (The Third Lorelai, season 1) -- Your wish is my command.</em>
+<h2>Generating HTML -- using commandline -- examples</h2><ul>
+<li>lore --config template=strange.tpl foo.html</li>
+
+<li>lore --docsdir doc/howto/</li>
+
+<li>lore -p --docsdir doc/howto/ # use plain progress</li>
+
+</ul>
+<hr />
+<em>Jackson (A Deep-Fried Korean Thanksgiving, season 3) -- Deep-fried cake!</em>
+<h2>Generating HTML -- using commandline -- examples (cont'd)</h2><ul>
+<li>lore --docsdir doc/howto/ --config baseurl=../api/%s.html</li>
+
+<li>lore --ext='' foo.html # produce 'foo' as output</li>
+
+</ul>
+<hr />
+<em>Jackson (A Deep-Fried Korean Thanksgiving, season 3) -- Deep-fried shoe!</em>
+<h2>Generating HTML -- notes about stylesheets</h2><ul>
+<li>Many 'class's in the output</li>
+
+<li>The stylesheet Twisted uses can be used as example<ul><li>Especially the .py-src-* classes: used for syntax highlighting</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Miss Patty (Cinnamon's Wake, season 1) -- If you had a better hair style I might consider dating</em>
+<h2>Generating LaTeX -- examples</h2><ul>
+<li>lore -olatex --config section file.html</li>
+
+<li>lore -olatex --config book file.html</li>
+
+<li>lore -olatex --config section --docsdir doc/howto/</li>
+
+</ul>
+<hr />
+<em>Luke (Hammers and Veils, season 2) -- Just an example</em>
+<h2>Using Lint -- notes</h2><ul>
+<li>If there is an element which lint gives a warning you disagree with: &lt;element hlint="off"&gt;mistake&lt;/element&gt;</li>
+
+<li>But usually the linter is right</li>
+
+<li>lint exits with non-zero status iff some document was not clean -- useful in shell scripts</li>
+
+</ul>
+<hr />
+<em>Paris (The Deer-Hunters, season 1) -- That would be cause for concern.</em>
+<h2>Understanding Lint Warnings</h2><ul>
+<li>Format: file:line:column:warning</li>
+
+<li>Line/column always point to start/end of element</li>
+
+<li>Some justifications:<ul><li>&lt;pre&gt; with &gt;80 characters/line renders badly, both in HTML and in LaTeX</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Jess (Teach Me Tonight, season 2) -- I appreciate the warning.</em>
+<h2>Using Lore For Slides</h2><ul>
+<li>lore -ilore-slides -omgp file.html for magic point</li>
+
+<li>lore -ilore-slides -oprosper file.html for prosper</li>
+
+<li>lore -ilore-slides -ohtml file.html for HTML next/prev</li>
+
+<li>Splits on 'h2'</li>
+
+<li>Dogfooding</li>
+
+</ul>
+<hr />
+<em>Emily (Road Trip to Harvard, season 2) -- Why in the world do you insist on taking slides?</em>
+<h2>Extending Lore</h2><ul>
+<li>Accept more input tags</li>
+
+<li>Change how documents are processed</li>
+
+<li>Add more output formats</li>
+
+</ul>
+<hr />
+<em>Rory (The Lorelais' First Day at Chilton, season 1) -- Well, add a couple of plaid skirts</em>
+<h2>Extending Lore -- example</h2><ul>
+<li>We want to add a way to blink: &lt;span class="blink"&gt;</li>
+
+<li>Modify HTML output</li>
+
+<li>Modify lint output</li>
+
+<li>Make it 'small caps' in LaTeX</li>
+
+<li>Distribute as package 'blinker'</li>
+
+</ul>
+<hr />
+<em>Lorelai (Presenting Lorelai Gilmore, season 2) -- No, no, if you wanna do it, I'll help. It's just weird.</em>
+<h2>Extending Lore -- example (cont'd)</h2>
+<pre>
+# blinker/html.py
+from twisted.lore import tree
+from twisted.web import microdom, domhelpers
+
+def doBlink(document):
+ for node in domhelpers.findElementsWithAttribute(document, 'class',
+ 'blink'):
+ newNode = microdom.Element('blink')
+ newNode.children = node.children
+ node.parentNode.replaceChild(newNode, node)
+
+def doFile(fn, docsdir, ext, url, templ, linkrel=''):
+ doc = tree.parseFileAndReport(fn)
+ doBlink(doc)
+ cn = templ.cloneNode(1)
+ tree.munge(doc, cn, linkrel, docsdir, fn, ext, url)
+ cn.writexml(open(os.path.splitext(fn)[0]+ext, 'wb'))
+</pre>
+
+<hr />
+<em>Christopher (Presenting Lorelai Gilmore, season 2) -- I can't believe you're letting her do it.</em>
+<h2>Extending Lore -- example (cont'd 2)</h2>
+<pre>
+# blinker/latex.py
+class BlinkerLatexSpitter(latex.LatexSpitter):
+
+ def visitNode_span_blink(self, node):
+ self.writer('{\sc ')
+ self.visitNodeDefault(node)
+ self.writer('}')
+</pre>
+
+<hr />
+<em>Lorelai (Presenting Lorelai Gilmore, season 2) -- I'm sorry, I meant what scenario on my planet</em>
+<h2>Extending Lore -- example (cont'd 3)</h2>
+<pre>
+# blinker/factory.py
+from blinker import html, latex
+from twisted.lore import default
+
+class ProcessingFunctionFactory(default.ProcessingFunctionFactory):
+
+ doFile = [doFile]
+
+ latexSpitters = {None: latex.BlinkLatexSpitter}
+
+ def getLintChecker(self):
+ checker = lint.getDefaultChecker()
+ checker.allowedClasses = checker.allowedClasses.copy()
+ oldSpan = checker.allowedClasses['span']
+ checker.allowedClasses['span'] = (lambda x:oldSpan(x) or
+ x=='blink')
+ return checker
+
+factory = ProcessingFunctionFactory()
+</pre>
+<pre>
+# blinker/plugins.tml
+register("Blink-Lore",
+ "blinker.factory",
+ description="Lore format with blink",
+ type="lore",
+ tapname="blinklore")
+</pre>
+<p>...and that's it!</p>
+
+<hr />
+<em>Rory (Presenting Lorelai Gilmore, season 2) -- Sorry, we haven't tamed my wild ways yet.</em>
+<h2>Man page support</h2><ul>
+<li>No output</li>
+
+<li>Man-&gt;Lore conversion</li>
+
+<li>lore -iman -olint file.1 --&gt; generates file.html</li>
+
+</ul>
+<hr />
+<em>Lorelai (Concert Interruptus, season 1) -- would you like to write out some sort of instruction manual to go with the dishes?</em>
+<h2>Man page support</h2><ul>
+<li>No output</li>
+
+<li>Man-&gt;Lore conversion</li>
+
+<li>lore -iman -olint file.1 --&gt; generates file.html</li>
+
+</ul>
+<hr />
+<em>Lorelai (Concert Interruptus, season 1) -- would you like to write out some sort of instruction manual to go with the dishes?</em>
+
+</body></html>
diff --git a/doc/historic/2003/europython/slides-template.tpl b/doc/historic/2003/europython/slides-template.tpl
new file mode 100644
index 0000000..fd33fc3
--- /dev/null
+++ b/doc/historic/2003/europython/slides-template.tpl
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+ <title></title>
+ <link type="text/css" rel="stylesheet" href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ [<span class="navigation"><a class="next"></a></span> |
+ <span class="navigation"><a class="previous"></a></span>]
+ <h1 class="title"></h1>
+ <div class="body">
+
+ </div>
+ </body>
+</html>
diff --git a/doc/historic/2003/europython/tw-deploy.html b/doc/historic/2003/europython/tw-deploy.html
new file mode 100644
index 0000000..628d12a
--- /dev/null
+++ b/doc/historic/2003/europython/tw-deploy.html
@@ -0,0 +1,1106 @@
+<html><head><title>A Twisted Web Tutorial</title></head><body>
+
+<h1>A Twisted Web Tutorial</h1>
+
+<h2>Twisted Web -- The Tutorial</h2><ul>
+<li>Welcome</li>
+
+<li>Gimmick -- Buffy quotes</li>
+
+</ul>
+<hr />
+<em>Sweet (Once More With Feeling, season 6) -- Showtime</em>
+<h2>Twisted Web</h2><ul>
+<li>Web server, using Twisted<ul><li>Like Apache, Zope...</li>
+</ul></li>
+
+<li>Serve static files</li>
+
+<li>Run CGIs</li>
+
+<li>Other uses...</li>
+
+</ul>
+<hr />
+<em>Giles (I Robot -- You Jane, season 1) -- There's a demon in the internet</em>
+<h2>Short Example: Putting a Server Up</h2><ul>
+<li>Here's all you need to know to bring up a server</li></ul>
+
+<pre class="shell">
+% mktap --uid=33 --gid=33 web --path=/var/www/htdocs --port=80
+% sudo twistd -f web.tap
+</pre>
+<ul><li>The rest of the talk will explain what that means...</li>
+
+<li>...and how to do more complicated things</li>
+
+</ul>
+<hr />
+<em>Buffy (Once More, With Feeling, season 6) -- I've got a theory. It doesn't matter.</em>
+<h2>Setup and Configuration Utilities</h2><ul>
+<li>mktap</li>
+
+<li>twistd</li>
+
+<li>websetroot<ul><li>Won't be covered, unless there is time</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Xander (The Harvest, season 1) -- crosses, garlic, stake through the heart</em>
+<h2>Digression: What are TAPs</h2><ul>
+<li>Pickled 'application configuration'</li>
+
+<li>Object which contains all the information about application</li>
+
+<li>The canonical way to represent configurations in Twisted</li>
+
+<li>Machine editable</li>
+
+</ul>
+<hr />
+<em>Master (The Wish, season 3) -- Behold the technical wonder</em>
+<h2>mktap</h2><ul>
+<li>General usage</li>
+
+<li>Flexibility and Power</li>
+
+</ul>
+<hr />
+<em>Buffy (Bad Eggs, season 2) -- I'm gonna need a *big* weapon</em>
+<h2>mktap web: Common Useful Options</h2><ul>
+<li>--path: serve from given path</li>
+
+<li>--port: listen on given port</li>
+
+<li>--user: serve from users' directories and personal servers</li>
+
+<li>--logfile: log to NCSA compatible logfile given</li>
+
+<li>--processor: add a special processor for a given extension</li>
+
+</ul>
+<hr />
+<em>Buffy (Bad Eggs, season 2) -- That's probably not gonna be the winning argument, is it?</em>
+
+<h2>twistd</h2><ul>
+<li>Start a Twisted Application</li>
+
+<li>Loads an instance of twisted.internet.app.Application from a file</li>
+
+<li>Daemonizes, binds to appropriate ports, and starts the Twisted mainloop</li>
+
+</ul>
+<hr />
+<em>Giles (Teacher's Pet, season 1) -- That's all he said? Fork Guy?</em>
+<h2>What's a Resource?</h2><ul>
+<li>Everything represented as twisted.web.resource.Resource</li>
+
+<li>Important interface:<ul><li>getChild()</li>
+
+<li>render()</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Sean (Go Fish, season 3) -- You're soakin' in it, bud.</em>
+<h2>Resource Examples</h2><ul>
+<li>Files<ul><li>MIME</li>
+
+<li>Processors</li>
+</ul></li>
+
+<li>Others<ul><li>Virtual hosts</li>
+
+<li>User directories</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Xander (As You Were, season 6) -- We have friends, family and demons</em>
+<h2>Web Development</h2><ul>
+<li>Processors<ul><li>Inherited from resource.Resource</li>
+
+<li>Interpret files as code rather than data</li>
+</ul></li>
+
+<li>Default processors<ul><li>.php -- default PHP</li>
+
+<li>.cgi -- Common Gateway Interface</li>
+
+<li>.rpy -- Correct way, Python scripting</li>
+
+<li>.trp -- Resource pickles</li>
+<li>...more</li>
+</ul></li>
+
+<li>You can also write your own</li>
+
+</ul>
+<hr />
+<em>Xander (Family, season 5) -- That was a tangled web</em>
+<h2>Custom Processor</h2><ul>
+<li>A custom processor to handle Perl CGIs (in a module called PerlScript)</li></ul>
+
+<pre class="python">
+from twisted.web import static, twcgi
+
+class PerlScript(twcgi.FilteredScript):
+ filter = '/usr/bin/perl' # Points to the perl parser
+</pre>
+<ul><li>Use:<ul><li>mktap web --path=/home/nafai/public_html --processor=.pl=PerlScript.PerlScript</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Tara (Family, season 4) -- There was the front of a camel</em>
+<h2>Resource Scripting</h2><ul>
+<li>Subclass resource.Resource</li>
+
+<li>Write a render(self, request) method<ul><li>Return string for immediate response</li>
+
+<li>Return NOT_DONE_YET and write to request</li>
+</ul></li>
+
+<li>Create an .rpy file that sets 'resource' to an instance</li>
+
+</ul>
+<hr />
+<em>Tara (Once More, With Feeling, season 6) -- You make me complete</em>
+<h2>.rpy example</h2>
+<pre class="python">
+from twisted.web import resource as resourcelib
+
+class MyGreatResource(resourcelib.Resource):
+ def render(self, request):
+ return "&lt;html&gt;foo&lt;/html&gt;"
+
+resource = MyGreatResource()
+</pre>
+<hr />
+<em>Willow (Welcome to the Hellmouth, season 1) -- It's probably easy for you.</em>
+<h2>Alternative Configuration Formats</h2><ul>
+<li>xml</li>
+
+<li>source</li>
+
+<li>Python</li>
+
+</ul>
+<hr />
+<em>Ben (The Gift, season 5) -- I wish there was another way</em>
+<h2>Alternative Configuration Formats -- Python</h2><ul>
+<li>Write manually</li>
+
+<li>Just uses twisted.web API</li>
+
+<li>Possible to do anything<ul><li>Write loops</li>
+
+<li>Read other files</li>
+
+<li>(not recommended) Define functions or classes</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Buffy (The I In Team, season 4) -- But I've learned that it pays to be flexible in life.</em>
+
+<h2>Python Configuration Example</h2><ul>
+
+<li>Create application</li>
+
+<li>Make it listen on port 80 for web requests...</li>
+
+<li>...which should be served from /var/www/htdocs</li></ul>
+
+<pre class="python">
+from twisted.internet import app
+from twisted.web import static, server
+
+application = app.Application('web')
+application.listenTCP(80,
+ server.Site(static.File("/var/www/htdocs")))
+</pre>
+<hr />
+<em>Willow (The Pack, season 1) -- It's simple, really.</em>
+
+<h2>Bannerfish -- A Case Study in Deployment</h2><ul>
+<li>Serves banner ads</li>
+
+<li>Has algorithms to maintain randomness and fairness</li>
+
+<li><a href="http://itamarst.org/software/bannerfish/">http://itamarst.org/software/bannerfish/</a></li>
+
+<li>But how to deploy?</li>
+
+</ul>
+<hr />
+<em>Xander (Halloween, season 2) -- Let's move out.</em>
+<h2>Bannerfish -- Standalone tap</h2><ul>
+<li>mktap bannerfish</li>
+
+<li>twistd -f bannerfish.tap</li>
+
+<li>For full list of options<ul><li>mktap --help bannerfish</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Ethan (Halloween, season 2) -- Don't wish to blow my own trumpet, but --</em>
+<h2>Bannerfish -- Standalone tap (behind reverse proxy)</h2><ul>
+<li>mktap bannerfish --port 81 --proxyhost=example.com</li>
+
+<li>twistd -f bannerfish.tap</li>
+
+<li>Now can work on internal server behind firewall</li>
+
+<li>If main server is Twisted Web, following Resource script will serve from bannerfish</li></ul>
+
+<pre class="python">
+resource = proxy.ReverseProxyResource('localhost', 81, '/')
+</pre>
+<hr />
+<em>Buffy (Halloween, season 2) -- You're sweet. A terrible liar, but sweet.</em>
+<h2>Bannerfish -- Standalone Python</h2>
+<pre class="python">
+from twisted.internet import app
+from twisted.cred import authorizer
+from twisted.web import server
+from bannerfish import service
+
+application = app.Application("bannerfish")
+auth = authorizer.DefaultAuthorizer(app)
+svc = service.BannerService('/var/bannerfish',
+ "bannerfish", application, auth)
+site = server.Site(svc.buildResource(None, None))
+application.listenTCP(80, site)
+</pre>
+<hr />
+<em>Spike (Halloween, season 2) -- Shaking. Terrified. Alone. Lost little lamb.</em>
+<h2>Bannerfish -- /etc/twisted-web/local.d Drop In</h2>
+<pre class="python">
+from twisted.cred import authorizer
+from bannerfish import service
+
+auth = authorizer.DefaultAuthorizer(app)
+svc = service.BannerService('/var/bannerfish',
+ "bannerfish", application, auth)
+resource = svc.buildResource(None, None)
+default.addChild("bannerfish", resource)
+</pre>
+<hr />
+<em>Cordelia (Halloween, season 2) -- Well, I guess you better get them back to their parents.</em>
+<h2>Bannerfish -- Resource Script</h2>
+<pre class="python">
+from twisted.cred import authorizer
+from twisted.internet import app
+from bannerfish import service
+
+application = registry.getComponent(app.Application)
+auth = authorizer.DefaultAuthorizer(application)
+svc = service.BannerService('/var/bannerfish',
+ "bannerfish", application, auth)
+resource = svc.buildResource(None, None)
+</pre>
+<ul><li>But see later, about registry</li>
+
+</ul>
+<hr />
+<em>Xander (Innocence, season 2) -- They like to see the big guns.</em>
+<h2>Bannerfish -- Distributed (Slave)</h2>
+
+<pre class="python">
+from twisted.internet import application
+from twisted.cred import authorizer
+from twisted.web import server
+from bannerfish import service
+application = app.Application("bannerfish")
+auth = authorizer.DefaultAuthorizer(application)
+svc = service.BannerService('/var/bannerfish',
+ "bannerfish", application, auth)
+site = server.Site(svc.buildResource(None, None))
+fact = pb.BrokerFactory(site)
+site = server.Site(root)
+application.listenUNIX('/var/run/bannerfish', fact)
+</pre>
+<h2>Bannerfish -- Distributed (Master, Resource Script)</h2>
+
+<pre class="python">
+from twisted.web import distrib
+
+resource = distrib.ResourceSubscription('unix',
+ '/var/run/bannerfish')
+</pre>
+<hr />
+<em>Oz (Innocence, season 2) -- So, do you guys steal weapons from the Army a lot?</em>
+<h2>Bannerfish -- Other options</h2><ul>
+<li>Mix and match possible</li>
+
+<li>Can serve same content multiple ways simultaneously</li>
+
+<li>Might be useful as a way to serve same ads different ways</li>
+
+<li>...or serve ads from several bannerfish servers...</li>
+
+<li>...each deployed differently.</li>
+
+</ul>
+<hr />
+<em>Buffy (Tabula Rasa, season 6) -- I'm like a superhero or something</em>
+<h2>Bannerfish -- Conclusions</h2><ul>
+<li>What are the tradeoffs?</li>
+
+<li>Everything works in the simple cases</li>
+
+<li>Not enough complicated cases to have data</li>
+
+<li>Luckily, easy to move between them</li>
+
+<li>Motto -- move deployment choices as late as possible</li>
+
+</ul>
+<hr />
+<em>Giles (Killed By Death, season 2) -- Simple enough, but, but</em>
+<h2>Further Reading</h2><ul>
+<li>Short overview -- doc/howto/web-overview.html</li>
+
+<li>In depth review -- doc/howto/using-twistedweb.html</li>
+
+<li>Using databases -- doc/howto/enterprise.html</li>
+
+<li>Deferred execution -- doc/howto/deferred.html</li>
+
+<li>Resource script examples -- doc/examples/*.rpy.py</li>
+
+</ul>
+<hr />
+<em>Giles (I Was Made to Love You, Season 5) -- There's an enormous amount of research we should do before -- no I'm lying</em>
+<h2>Questions?</h2>
+<em>Vampire Willow (Dopplegangland, season 3): Questions? Comments?</em>
+<h2>Bonus Slides</h2>
+<em>Xander (The Dark Age, season 2) -- A bonus day of class plus Cordelia.</em>
+
+<h2>Python Configuration -- Hints</h2><ul>
+<li>Working with persistence</li>
+
+<li>Processors</li>
+
+<li>Indices</li>
+
+<li>Virtual Hosts</li>
+
+</ul>
+<hr />
+<em>Buffy (Phases, season 2) -- Have you dropped any hints?</em>
+<h2>Python Configuration -- Persistence</h2><ul>
+<li>Don't define functions or classes</li>
+
+<li>Don't modify class attributes</li>
+
+</ul>
+<hr />
+<em>Spike (Once More, With Feeling) -- Let me rest in peace</em>
+<h2>Python Configuration -- Processors</h2>
+<pre class="python">
+
+from twisted.internet import app
+from twisted.web import static, server
+from twisted.web import twcgi
+
+root = static.File("/var/www")
+root.processors = {".cgi": twcgi.CGIScript}
+application = app.Application('web')
+application.listenTCP(80, server.Site(root))
+</pre>
+<hr />
+<em>Manny (Doublemeat Palace, season 6) -- It's a meat process</em>
+<h2>Python Configuration -- Indices</h2>
+<pre class="python">
+
+root = static.File("/var/www")
+root.indices = ['index.rpy', 'index.html']
+</pre>
+<hr />
+<em>Willow (Buffy vs. Dracula, season 5) -- Labelling your amulets and indexing your diaries</em>
+<h2>Python Configuration -- Virtual Hosts</h2>
+<pre class="python">
+
+from twisted.web import vhost
+default = static.File("/var/www")
+foo = static.File("/var/foo")
+root = vhost.NamedVirtualHost(default)
+root.addHost('foo.com', foo)
+</pre>
+<hr />
+<em>Fritz (I Robot, You Jane, season 1) -- The only reality is virtual.</em>
+<h2>Python Configuration -- uber example</h2>
+<pre class="python">
+
+from twisted.internet import app
+from twisted.web import static, server, vhost, script
+
+default = static.File("/var/www")
+default.processors = {".rpy", script.ResourceScript}
+root = vhost.NamedVirtualHost(default
+foo = static.File("/var/foo")
+foo.indices = ['index.xhtml', 'index.html']
+root.addHost('foo.com', foo)
+site = server.Site(root)
+application = app.Application('web')
+application.listenTCP(80, site, interface='127.0.0.1')
+</pre>
+<hr />
+<em>Buffy (Potential, season 7) -- It was putting a lot of stock in that uber-vamp</em>
+<h2>Python Configuration -- Splitting With Reverse Proxy</h2><ul>
+<li>Original use case - SVN</li></ul>
+
+<pre class="python">
+from twisted.web import proxy
+
+root.putChild('foo',
+ proxy.ReverseProxyResource('localhost',
+ 81, '/foo/'))
+</pre>
+<hr />
+<em>Buffy (Once More, With Feeling, season 6 -- So I will walk through the fire</em>
+<h2>mktap examples</h2><ul>
+<li>mktap web</li>
+
+<li>mktap web --path=/var/www --logfile=/var/log/twistedweb.log</li>
+
+<li>mktap web --port=80 --path=/var/www --mime-type=text/plain</li>
+
+<li>mktap web --path=/home/nafai/public_html --processor=.pl=PerlProcessor.PerlProcessor --index=index.pl</li>
+
+</ul>
+<hr />
+<em>Anya (I Was Made to Love You, season 4) -- You can also see the website I designed for the magic shop</em>
+<h2>mktap examples (cont'd)</h2><ul>
+<li>mktap web --users</li>
+
+<li>mktap web --ignore-ext=.cgi</li>
+
+<li>mktap web --index=index.cgi --index=index.rpy --index=index.html</li>
+
+</ul>
+<hr />
+<em>Buffy (Once More, With Feeling, season 6) -- All the twists and bends</em>
+<h2>mktap examples (alternate formats)</h2><ul>
+<li>mktap --type=source web</li>
+
+<li>mktap --type=xml web</li>
+
+</ul>
+<hr />
+<em>Tara (Seeing Red, season 6) -- It isn't written in any ancient language we could identify.</em>
+<h2>mktap examples (setting uid)</h2><ul>
+<li>mktap --uid=33 web</li>
+
+<li>mktap --gid=33 web</li>
+
+<li>Uid/Gid of www-data on Debian systems</li>
+
+<li>Not possible to use username</li>
+
+<li>More about this later</li>
+
+</ul>
+<hr />
+<em>Buffy (Who Are You?, season 4) -- I would be Buffy</em>
+
+<h2>twistd examples</h2><ul>
+<li>twistd -f web.tap -l /var/log/twisted.log</li>
+
+<li>twistd -f web.tap --pidfile /var/run/web.pid</li>
+
+<li>twistd -x web.tax<ul><li>For mktap --type=xml</li>
+</ul></li>
+
+<li>twistd -s web.tas<ul><li>For mktap --type=source</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Xander (Teacher's Pet, season 1) -- How come *that* never came up?</em>
+<h2>Shutting down twistd</h2><ul>
+<li>On Unix (in general): <ul><li>kill `cat twistd.pid`</li>
+</ul></li>
+
+<li>On Windows: <ul><li>Cannot daemonize on Windows, so just run twistd in a command prompt</li>
+
+<li>Switch to the command prompt, and press Control-C</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Buffy (Prophecy Girl, season 1) -- I don't wanna die.</em>
+<h2>Shutdown TAPs</h2><ul>
+<li>Since TAPs store persistent data for an application, a 'shutdown' TAP is created on twistd shutdown</li>
+
+<li>You'll often want to start your Twisted application on subsequent runs with the shutdown TAP</li>
+
+</ul>
+<hr />
+<em>Headstone (The Gift, season 5) -- She saved the world. A lot.</em>
+<h2>twistd and security</h2><ul>
+<li>When twistd is run as root, it will shed root privileges for the uid and gid of either the user that created the TAP or those specified on the mktap commandline.</li>
+
+</ul>
+<hr />
+<em>Buffy (Dopplegangland, season 3) -- I think it's good to be reliable</em>
+
+<h2>Resource Call Examples</h2><ul>
+<li>/foo/bar/baz gets converted to:</li></ul>
+
+<pre class="python">
+site.getChild('foo', request
+ ).getChild('bar', request
+ ).getChild('baz', request
+ ).render(request)
+</pre>
+<hr />
+<em>Willow/Tara (Afterlife, Part 2, season 6) -- Child of words, hear thy makers</em>
+<h2>Resource Call Examples (cont'd)</h2><ul>
+<li>/foo/bar/baz/ gets converted to:</li></ul>
+
+<pre class="python">
+site.getChild('foo', request
+ ).getChild('bar', request
+ ).getChild('baz', request
+ ).getChild('', request
+ ).render(request)
+</pre>
+<hr />
+<em>Buffy (Gone, season 6) -- Stop trying to see me.</em>
+<h2>Distributed Servers -- Theory</h2><ul>
+<li>Master is a resource</li>
+
+<li>Slave is a server</li>
+
+<li>Same server can have both master and slave parts</li>
+
+</ul>
+<hr />
+<em>Anya (Once More, With Feeling, season 6) -- I've got a theory, it could be bunnies</em>
+<h2>Distributed Servers -- Manually</h2>
+<pre class="python">
+from twisted.internet import app, protocol
+from twisted.web import server, distrib, static
+from twisted.spread import pb
+
+application = app.Application("silly-web")
+# The "master" server
+site = server.Site(distrib.ResourceSubscription('unix', '.rp'))
+application.listenTCP(19988, site)
+# The "slave" server
+fact = pb.BrokerFactory(distrib.ResourcePublisher(
+ server.Site(static.File('static'))))
+application.listenUNIX('./.rp', fact)
+</pre>
+<hr />
+<em>Buffy (Some Assembly Required, season 2) -- Men dig up the corpses and the women have the babies.</em>
+<h2>Distributed Servers -- Manual (cont'd)</h2><ul>
+<li>First Server</li></ul>
+
+<pre class="python">
+from twisted.internet import app, protocol
+from twisted.web import server, distrib, static, vhost
+from twisted.spread import pb
+
+application = app.Application("ping-web")
+
+default = static.File("/var/www/foo")
+root = vhost.NamedVirtualHost(default)
+root.addVhost("foo.com", default)
+bar = distrib.ResourceSubscription('unix', '.bar')
+root.addVhost("bar.com", bar)
+
+fact = pb.BrokerFactory(static.Site(default))
+site = server.Site(root)
+application.listenTCP(19988, site)
+application.listenUNIX('./.foo', fact)
+</pre>
+<hr />
+<em>Buffy (Welcome to the Hellmouth, season 1) -- Now, we can do this the hard way, or...</em>
+<h2>Distributed Servers -- Manual (cont'd 2)</h2><ul>
+<li>Second Server</li></ul>
+
+<pre class="python">
+from twisted.internet import app, protocol
+from twisted.web import server, distrib, static, vhost
+from twisted.spread import pb
+
+application = app.Application("pong-web")
+
+foo = distrib.ResourceSubscription('unix', '.foo')
+root = vhost.NamedVirtualHost(foo)
+root.addVhost("foo.com", foo)
+bar = static.File("/var/www/bar")
+root.addVhost("bar.com", bar)
+
+fact = pb.BrokerFactory(static.Site(bar))
+site = server.Site(root)
+application.listenTCP(19989, site)
+application.listenUNIX('./.bar', fact)
+</pre>
+<hr />
+<em>Buffy (Welcome to the Hellmouth, season 1) -- ...well, actually there's just the hard way.</em>
+<h2>Distributed Servers -- User Directory</h2><ul>
+<li>A resource</li>
+
+<li>Child that looks like 'moshez' -- ~moshez/public_html </li>
+
+<li>Child that looks like 'moshez.twistd' -- moshez's personal server</li>
+
+</ul>
+<hr />
+<em>Master (The Wish, season 3) -- Mass production!</em>
+<h2>Distributed Servers -- User Directory Server</h2><ul>
+<li>With mktap: mktap web --user</li>
+
+<li>With Python configuration</li></ul>
+
+<pre class="python">
+from twisted.internet import app
+from twisted.web import static, server, distrib
+
+root = static.File("/var/www")
+root.putChild("users", distrib.UserDirectory())
+site = server.Site(root)
+application = app.Application('web')
+application.listenTCP(80, site)
+</pre>
+<hr />
+<em>Richard (Reptile Boy, season 2) -- In his name.</em>
+<h2>Distributed Servers -- Personal Servers</h2><ul>
+<li>With mktap: mktap web --personal ...</li>
+
+<li>With Python configuration</li></ul>
+
+<pre class="python">
+from twisted.internet import app
+from twisted.web import static, server, distrib
+from twisted.spread import pb
+
+root = static.File("/home/moshez/twistd")
+site = server.Site(root)
+
+fact = pb.BrokerFactory(distrib.ResourcePublisher(site))
+application.listenUNIX('/home/moshez/.twisted-web-pb', fact)
+</pre>
+<hr />
+<em>Giles (Bargaining, season 6) -- It's my personal collection</em>
+<h2>Debian Configuration</h2><ul>
+<li>Inside twisted-web package</li>
+
+<li>Goal -- look like other web servers to users</li>
+
+<li>Goal -- interoperate easily</li>
+
+<li>Goal -- allow users to avoid modifying files</li>
+
+</ul>
+<hr />
+<em>Buffy (Bad Girls, season 3) -- We can help each other.</em>
+<h2>Debian Configuration -- Usage</h2><ul>
+<li>Changing port -- edit /etc/twisted-web/ports</li>
+
+<li>Want to use behind reverse proxy? Use rptwisted</li>
+
+<li>Change anything else -- drop files in /etc/twisted-web/local.d</li>
+
+</ul>
+<hr />
+<em>Faith (Home Coming, season 3) -- we'll use 'em</em>
+<h2>Debian Configuration -- Drop In Examples</h2>
+<pre class="python">
+from twisted.web import static
+import os
+
+vhostDir = '/var/www/vhost/'
+
+for file in os.listdir(vhostDir):
+ root.addHost(file, static.File(os.path.join(vhostDir, file)))
+</pre>
+<hr />
+<em>Buffy (The Freshman, season 4) -- I just thought I'd drop in</em>
+<h2>Debian Configuration -- Drop In Examples (cont'd)</h2>
+<pre class="python">
+from twisted.web import script, static
+
+default.processors['.rpy'] = script.ResourceScript
+default.ignoreExt('rpy')
+</pre>
+<hr />
+<em>Riley (As You Were, season 6) -- Sorry to just drop in on you</em>
+<h2>Debian Configuration -- Drop In Examples (cont'd 2)</h2>
+<pre class="python">
+from twisted.web import vhost
+
+default.putChild('vhost', vhost.VHostMonsterResource())
+</pre>
+<hr />
+<em>Sam (As You Were, season 6) -- a hairy night drop into hostile territory</em>
+<h2>twistedmatrix.com Configuration</h2><ul>
+<li>Some highlights</li></ul>
+
+<pre class="python">
+...
+indexNames = ['index', 'index.html', 'index.xhtml', 'index.rpy','index.cgi']
+...
+root.putChild('mailman', twcgi.CGIDirectory('/usr/lib/cgi-bin'))
+root.putChild('users', distrib.UserDirectory())
+root.putChild('cgi-bin', twcgi.CGIDirectory('/usr/lib/cgi-bin'))
+root.putChild('doc', static.File('/usr/share/doc'))
+...
+uid = pwd.getpwnam('www-data')[2]
+gid = grp.getgrnam('www-data')[2]
+...
+top = rewrite.RewriterResource(root, rewrite.tildeToUsers)
+...
+application = app.Application("web", uid=uid, gid=gid)
+</pre>
+<hr />
+<em>Xander (The Witch, season 1) -- May all lesser cretins bow before me.</em>
+<h2>Apache vs. Twisted Web</h2><ul>
+<li>Apache is faster</li>
+
+<li>Apache -- Threads/processes model</li>
+
+<li>Twisted -- async model</li>
+
+<li>Apache -- has C security holes (buffer overflows)</li>
+
+<li>Twisted -- easy to set up</li>
+
+<li>Twisted -- built in Python programmability</li>
+
+</ul>
+<hr />
+<em>Willow (Buffy vs. Dracular, season 5) -- I think we've just put our finger on why we're the sidekicks</em>
+<h2>Apache/Twisted Web Integration</h2><ul>
+<li>Use both!</li>
+
+<li>Apache's reverse proxy works well</li>
+
+<li>Easy to have a site which is partially managed by Apache</li>
+
+<li>Documentation has examples of configurations</li>
+
+</ul>
+<hr />
+<em>Xander (What's My Line, season 2) -- Angel's our friend! Except I don't like him.</em>
+<h2>Zope vs. Twisted Web</h2><ul>
+<li>Zope -- fully editable through the web</li>
+
+<li>Zope -- uses ZODB, not file system</li>
+
+<li>Twisted -- can integrate with other protocols easily</li>
+
+<li>Twisted -- extension code has much less overhead</li>
+
+</ul>
+<hr />
+<em>Willow (Dopplegangland, season 3) -- Competition is natural and healthy</em>
+<h2>Zope/Twisted Web Integration</h2><ul>
+<li>Possible to use Twisted as Zope's network layer</li>
+
+<li>Hackish with Zope2</li>
+
+<li>Easier with Zope3</li>
+
+</ul>
+<hr />
+<em>Snyder (Dopplegangland, season 3) -- It's a perfect match.</em>
+<h2>Zope/Twisted Web Integration (cont'd)</h2><ul>
+<li>Less direct -- use Apache</li>
+
+<li>Reverse proxy parts to Zope</li>
+
+<li>Reverse proxy parts to Twisted Web</li>
+
+</ul>
+<hr />
+<em>Wesley (Dopplegangland, season 3) -- Still a little sloppy, though</em>
+<h2>Applications Appropriate for Twisted Web</h2><ul>
+<li>Webmail</li>
+
+<li>Blogs</li>
+
+<li>Web/other protocol chat systems</li>
+
+</ul>
+<hr />
+<em>Sweet (Once More, With Feeling) -- Why don't you come and play?</em>
+<h2>Behind Reverse Proxy</h2><ul>
+<li>Sometimes, we want Twisted to pretend to be another host/port</li>
+
+<li>Reverse proxies, NATs, etc.</li>
+
+<li>Reverse proxy to /vhost/http/&lt;host:port&gt;/</li>
+
+<li>Make sure root has a child called vhost of type twisted.web.vhost.VirtualHostingMonster</li>
+
+</ul>
+<hr />
+<em>Jenny (I Robot -- You Jane, season 1) -- The divine exists in cyberspace</em>
+<h2>Rewrite Rules</h2><ul>
+<li>Change a URL to another</li>
+
+<li>Useful for different treatment from outside resources</li>
+
+<li>Wraps a resource</li>
+
+</ul>
+<hr />
+<em>Spike (What's My Line, season 2) -- Read it again.</em>
+<h2>Rewrite Rules -- Example</h2>
+<pre class="python">
+
+root = static.File("/var/www")
+root.putChild("users", distrib.UserDirectory())
+root = rewrite.RewriterResource(root, rewrite.tildeToUsers)
+</pre>
+<ul><li>Now, /~moshez/ works</li>
+
+</ul>
+<hr />
+<em>Spike (What's My Line, season 2) -- I think it's just enough kill.</em>
+<h2>websetroot</h2><ul>
+<li>Used to change what the root of the server points to</li>
+
+<li>Set it to a Resource contained either in a Python source file or a Pickle file</li>
+
+</ul>
+<hr />
+<em>Manny (DoubleMeat Palace, season 6) -- We have a lot of turnover here</em>
+<h2>Sample websetroot command lines</h2><ul>
+<li>websetroot -p 80 -f web.tap --script rootResource.py</li>
+
+<li>websetroot -p 8080 -f web.tap --pickle rootPickle</li>
+
+</ul>
+<hr />
+<em>Manny (DoubleMeat Palace, season 6) -- You can toss it</em>
+<h2>init.d</h2><ul>
+<li>pidfile</li>
+
+<li>chdir</li>
+
+<li>chroot?</li>
+
+<li>No smooth reloading</li>
+
+<li>Persistence</li>
+
+</ul>
+<hr />
+<em>Bob (Zeppo, season 3) -- He hasn't been initiated.</em>
+<h2>Special Bonus - How to Configure &lt;user&gt;.example.com</h2>
+<pre class="python">
+import pwd, os
+from twisted.web import resource, error, distrib
+from twisted.protocols import http
+
+class UserNameVirtualHost(resource.Resource):
+
+ def __init__(self, default, tail):
+ resource.Resource.__init__(self)
+ self.default = default
+ self.tail = tail
+ self.users = {}
+
+ def _getResourceForRequest(self, request):
+ host=request.getHeader('host')
+ if host.endswith(tail):
+ username = host[:-len(tail)]
+ else:
+ username = default
+ if self.users.has_key(username):
+ return self.users[username]
+ try:
+ (pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir,
+ pw_shell) = pwd.getpwnam(username)
+ except KeyError:
+ return error.ErrorPage(http.NOT_FOUND,
+ "No Such User",
+ "The user %s was not found on this system." %
+ repr(username))
+ twistdsock = os.path.join(pw_dir, ".twistd-web-pb")
+ rs = distrib.ResourceSubscription('unix',twistdsock)
+ self.users[username] = rs
+ return rs
+
+ def render(self, request):
+ resrc = self._getResourceForRequest(request)
+ return resrc.render(request)
+
+ def getChild(self, path, request):
+ resrc = self._getResourceForRequest(request)
+ request.path=request.path[:-1]
+ request.postpath=request.uri.split('/')[1:]
+ print request, request.path, request.postpath
+ return resrc.getChildForRequest(request)
+</pre>
+<hr />
+<em>Morgan (The Puppet Show, season 1) -- Weird? What d'you mean?</em>
+<h2>Special Bonus - How to Configure &lt;user&gt;.example.com (cont'd)</h2><ul>
+<li>Put above in a module (say, uservhost)</li>
+
+<li>Use following configuration file</li></ul>
+
+<pre class="python">
+from twisted.internet import app
+from twisted.web import server
+import uservhost
+
+root = UserNameVirtualHost("www", "example.com")
+site = server.Site(root)
+application = app.Application('web')
+application.listenTCP(80, site)
+</pre>
+<hr />
+<em>Snyder (The Puppet Show, season 1) -- You need to integrate into this school, people.</em>
+<h2>Using the Twisted Registry</h2><ul>
+<li>Use especially in Resource Scripts</li>
+
+<li>Save persistent information</li>
+
+<li>Uses Twisted's Componentized to be extensible</li>
+
+</ul>
+<hr />
+<em>Angel (Helpless, season 3) -- I wanted to keep it safe</em>
+<h2>Using the Twisted Registry -- example</h2>
+<pre class="python">
+
+from twisted.web import distrib
+
+resource = registry.getComponent(distrib.UserDirectory)
+if not resource:
+ resource = distrib.UserDirectory()
+ registry.setComponent(distrib.UserDirectory, resource)
+</pre>
+<hr />
+<em>Paul (The Freshman, season 4) -- Do you know where they're distributing the [...] applications?</em>
+<h2>Using the Twisted Registry -- problems</h2><ul>
+<li>In most cases -- need to write a custom class</li>
+
+<li>Saves data in-memory</li>
+
+<li>Won't work as expected unless -shutdown taps are used</li>
+
+</ul>
+<hr />
+<em>Anya (Once More, With Feeling, season 6) -- The only trouble is [pause] I'll never tell.</em>
+<h2>Alternative Configuration Formats -- XML</h2><ul>
+<li>Can be generated from mktap</li>
+
+<li>Editable with any XML editor</li>
+
+<li>Easy to do easy things<ul><li>Change a port</li>
+</ul></li>
+
+<li>Nontrivial to do hard things<ul><li>Bind to specific IP</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Buffy (Once More, With Feeling, season 6) -- To fit in in this glittering world.</em>
+<h2>Alternative Configuration Formats -- XML -- example</h2>
+<pre class="python">
+&lt;?xml version="1.0"?&gt;
+
+&lt;instance class="twisted.internet.app.Application" reference="1"&gt;
+ &lt;dictionary&gt;
+...
+ &lt;string role="key" value="tcpPorts" /&gt;
+ &lt;list&gt;
+ &lt;tuple&gt;
+ &lt;int value="80" /&gt;
+ &lt;instance class="twisted.web.server.Site"&gt;
+ &lt;dictionary&gt;
+...
+ &lt;string role="key" value="resource" /&gt;
+ &lt;instance class="twisted.web.static.File"&gt;
+ &lt;dictionary&gt;
+...
+ &lt;string role="key" value="path" /&gt;
+ &lt;string value="/var/www" /&gt;
+...
+ &lt;/dictionary&gt;
+ &lt;/instance&gt;
+...
+ &lt;/dictionary&gt;
+ &lt;/instance&gt;
+...
+ &lt;/tuple&gt;
+ &lt;/list&gt;
+...
+ &lt;/dictionary&gt;
+&lt;/instance&gt;
+</pre>
+<hr />
+<em>Natalie (Teacher's Pet, season 1) -- There's nothing ugly about these creatures</em>
+<h2>Alternative Configuration Formats -- Source</h2><ul>
+<li>Can be generated from mktap</li>
+
+<li>Editable with any Python source editor</li>
+
+<li>Easy to do easy things<ul><li>Change a port</li>
+</ul></li>
+
+<li>Nontrivial to do hard things<ul><li>Bind to specific IP</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Willow/Giles/Xander (Primeval, season 4) -- You could never hope to grasp the source</em>
+<h2>Alternative Configuration Formats -- Source -- Example</h2>
+<pre class="python">
+app=Ref(1,
+ Instance('twisted.internet.app.Application',{
+...
+ 'tcpPorts':[
+ (
+ 80,
+ Instance('twisted.web.server.Site',
+...
+ resource=Instance('twisted.web.static.File',{
+...
+ 'path':'/var/www',
+...
+ ),
+ ],
+...
+ }))
+</pre>
+<hr />
+<em>Tara (Family, season 5) -- You learn her source, and, uh we'll introduce her to her insect reflection</em>
+<h2>Twisted Web - Beginnings</h2>
+<pre class="python">
+
+&lt;glyphAtWork&gt; the http server was so we could say "Web!" if we ever did
+ a freshmeat announcement
+&lt;glyphAtWork&gt; this makes people excited
+</pre>
+<ul><li>Turned out he was right</li>
+
+</ul>
+<hr />
+<em>Dawn (Get It Done, season 7) -- I think it's an origin myth.</em>
+<h2>Woven Overview</h2><ul>
+<li>HTML templates</li>
+
+<li>Model/View/Controller architecture</li>
+
+<li>Integrated with deferred</li>
+
+<li>Classical systems work badly with async</li>
+
+<li>More -- beyond scope of this tutorial</li>
+
+</ul>
+<hr />
+<em>Razor (Bargaining, season 6) -- A pretty toy</em>
+</body></html>
diff --git a/doc/historic/2003/europython/twisted.html b/doc/historic/2003/europython/twisted.html
new file mode 100644
index 0000000..2576bca
--- /dev/null
+++ b/doc/historic/2003/europython/twisted.html
@@ -0,0 +1,608 @@
+<html><head><title>Twisted Tutorial</title></head>
+<body>
+
+<h1>Twisted Tutorial</h1>
+
+<h2>Twisted -- The Tutorial</h2><ul>
+<li>Welcome</li>
+
+<li>Gimmick -- Charmed quotes</li>
+
+</ul>
+<hr />
+<em>Prue (Something Wicca This Way Comes, season 1) -- Piper, the girl has no vision, no sense of the future.</em>
+<h2>Twisted -- Networking For Python</h2><ul>
+<li>Handles the icky socket stuff</li>
+
+<li>Handles the icky select stuff</li>
+
+<li>No threads, no blocking</li>
+
+</ul>
+<hr />
+<em>Leo (Bite Me, season 4) -- As far as I know they're apart of a whole different network now.</em>
+<h2>Finger</h2><ul>
+<li>Send username</li>
+
+<li>Get back some stuff about user</li>
+
+<li>Will only implement subset of protocol here</li>
+
+</ul>
+<hr />
+<em>Natalie (Blinded By the Whitelighter) -- I'll assume a demon attacked your finger</em>
+<h2>Finger - Protocol code</h2>
+<pre class="python">
+from twisted.protocols import basic
+
+class FingerClient(basic.LineReceiver):
+
+ # This will be called when the connection is made
+ def connectionMade(self): self.sendLine(self.factory.user)
+
+ # This will be called when the server sends us a line.
+ # IMPORTANT: line *without "\n" at end.
+ # Yes, this means empty line does not mean EOF
+ def lineReceived(self, line): print line
+
+ # This will be called when the connection is terminated
+ def connectionLost(self, _): print "-"*40
+</pre>
+<hr />
+<em>Phoebe (Blind Sided, season 1) -- Standard dating protocol.</em>
+<h2>Finger - client factory</h2><ul>
+<li>Keep configuration information</li>
+
+<li>In this case, just the username</li></ul>
+
+<pre class="python">
+from twisted.internet import protocol
+
+class FingerFactory(protocol.ClientFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, user): self.user = user
+
+ def clientConnectionFailed(self, _, reason):
+ print "error", reason.value
+</pre>
+
+<hr />
+<em>Jack (Ms. Hellfire, season 2) -- Well, they'd better be a rich client</em>
+<h2>Finger - tying it all together</h2><ul>
+<li>Actually run above code</li>
+
+<li>Use reactors</li></ul>
+
+<pre class="python">
+from twisted.internet import reactor
+import sys
+
+user, host = sys.argv[1].split('@')
+port = 79
+reactor.connectTCP(host, port, FingerFactory(port))
+reactor.run()
+</pre>
+<hr />
+<em>Prue/Phoebe/Piper (Something Wicca This Way Comes, season 1) -- The power of three will set us free</em>
+<h2>Finger - a bug</h2><ul>
+<li>Succeed or fail, program doesn't exit</li>
+
+<li>Reactor continues in a loop</li>
+
+<li>Takes almost no CPU time...</li>
+
+<li>...but still wrong behaviour</li>
+
+</ul>
+<hr />
+<em>Leo (Trial By Magic, season 4) -- Demons you can handle but not rats?</em>
+<h2>Digression - Deferreds</h2><ul>
+<li>In order to be more flexible, we want callbacks</li>
+
+<li>Common callbacks are too weak</li>
+
+<li>We used 'deferreds' as an abstraction for callbacks</li>
+
+</ul>
+<hr />
+<em>Piper (Morality Bites, season 2) -- Talk about it later.</em>
+<h2>Finger - reimplementing correctly</h2>
+<pre class="python">
+from twisted.protocols import basic
+from twisted.internet import protocol, defer
+import sys
+
+class FingerClient(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.transport.write(self.factory.user+"\n")
+
+ def lineReceived(self, line):
+ self.factory.gotLine(line)
+</pre>
+
+
+<h2>Finger - reimplementing correctly (cont'd)</h2>
+<pre class="python">
+class FingerFactory(protocol.ClientFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, user):
+ self.user, self.d = user, defer.Deferred()
+
+ def gotLine(self, line): print line
+
+ def clientConnectionLost(self, _, why): self.d.callback(None)
+
+ def clientConnectionFailed(self, _, why): self.d.errback(why)
+</pre>
+
+<h2>Finger - reimplementing correctly (cont'd 2)</h2>
+<pre class="python">
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ from twisted.python import util
+ user, host = sys.argv[1].split('@')
+ f = FingerFactory(user)
+ port = 79
+ reactor.connectTCP(host, port, FingerFactory(port))
+ f.d.addCallback(lambda _: reactor.stop())
+ f.d.addErrback(lambda _: (util.println("could not connect"),
+ reactor.stop()))
+ reactor.run()
+</pre>
+<hr />
+<em>Phoebe (Charmed and Dangerous, season 4) -- That's what we were missing.</em>
+<h2>Servers</h2><ul>
+<li>Servers are actually easier</li>
+
+<li>Servers meant to wait for events</li>
+
+<li>Most of concepts similar to clients</li>
+
+</ul>
+<hr />
+<em>Genie (Be Careful What You Witch For, season 2) -- All I know is that you rubbed and now I serve.</em>
+<h2>Finger - protocol</h2>
+<pre class="python">
+class FingerServer(basic.LineReceiver):
+
+ def lineReceived(self, line):
+ self.transport.write(self.factory.getUser(line))
+ self.transport.loseConnection()
+</pre>
+<hr />
+<em>Secretary (The Painted World, season 2) -- Well, you won't have any trouble with this if you figured that out.</em>
+<h2>Finger - factory</h2>
+<pre class="python">
+class FingerServerFactory(protocol.Factory):
+
+ protocol = FingerServer
+
+ def __init__(self):
+ self.users = {}
+ self.message = "No such user\n"
+
+ def getUser(self, name):
+ return self.users.get(name, self.message)
+
+ def setUser(self, user, status):
+ self.users[user] = status
+</pre>
+<hr />
+<em>Prue (The Demon Who Came In From the Cole, season 3) -- Okay, so who are they?</em>
+<h2>Finger - glue</h2>
+<pre class="python">
+factory = FingerServerFactory()
+factory.setUser("moshez", "Online - Sitting at computer\n")
+factory.setUser("spiv", "Offline - Surfing the waves\n")
+
+reactor.listenTCP(79, factory)
+</pre>
+<hr />
+<em>Prue (All Halliwell's Eve, season 3) -- Put it all together, it may just work.</em>
+<h2>Finger Server - problem</h2><ul>
+<li>What if server has to actually work to find user's status?</li>
+
+<li>For example, read status from a website</li>
+
+<li>API forces us to block -- not good</li>
+
+</ul>
+<hr />
+<em>Piper (All Halliwell's Eve, season 3) -- We've got big problems, a little time and a little magic.</em>
+<h2>Finger server -- new protocol</h2>
+<pre class="python">
+class FingerServer(basic.LineReceiver):
+
+ def lineReceived(self, line):
+ d = self.factory.getUser(line)
+ d.addCallback(self.writeResponse)
+ d.addErrback(self.writeError)
+
+ def writeResponse(self, response):
+ self.transport.write(response)
+ self.transport.loseConnection()
+
+ def writeError(self, error):
+ self.transport.write("Server error -- try later\n")
+ self.transport.loseConnection()
+</pre>
+<hr />
+<em>Piper (Ex Libris, season 2) -- We'll worry about it later.</em>
+<h2>Finger - factory</h2>
+<pre class="python">
+class FingerServerFactory(protocol.Factory):
+
+ protocol = FingerServer
+
+ def __init__(self):
+ self.users = {}
+ self.message = "No such user\n"
+
+ def getUser(self, name):
+ return defer.succeed(self.users.get(name, self.message))
+
+ def setUser(self, user, status):
+ self.users[user] = status
+</pre>
+<hr />
+<em>Piper/Zen Master (Enter the Demon, season 4) -- It is a different realm down there with new rules.</em>
+<h2>Finger - web factory</h2>
+<pre class="python">
+from twisted.web import client
+
+class FingerWebFactory(protocol.Factory):
+ protocol = FingerServer
+
+ def getUser(self, name):
+ url = "http://example.com/~%s/online" % name
+ d = client.getPage(url)
+ d.addErrback(lambda _: "No such user\n")
+ return d
+</pre>
+<hr />
+<em>Applicant #3 (The Painted World, season 2) -- in this day and age, who can't write in the HTML numeric languages, right?</em>
+<h2>Application</h2><ul>
+<li>The Twisted way of configuration files</li>
+
+<li>Decouple configuration from running</li></ul>
+
+<h2>Application (Example)</h2>
+<pre class="python">
+# File: finger.tpy
+from twisted.internet import app
+import fingerserver
+
+factory = fingerserver.FingerServerFactory()
+factory.setUser("moshez", "Online - Sitting at computer\n")
+factory.setUser("spiv", "Offline - Surfing the waves\n")
+application = app.Application("finger")
+application.listenTCP(79, factory)
+</pre>
+
+<hr />
+<em>Paige (Hell Hath No Fury, season 4) -- I am taking full responsibility for being late with the application.</em>
+<h2>twistd</h2><ul>
+<li>TWISTed Daemonizer</li>
+
+<li>Daemonizes Twisted servers</li>
+
+<li>Takes care of log files, PID files, etc.</li>
+
+<li>twistd -y finger.tpy</li>
+
+</ul>
+<hr />
+<em>Phoebe (Sleuthing With the Enemy, season 3) -- Was it some sick twisted demonic thrill?</em>
+<h2>twistd examples</h2><ul>
+<li>twistd -y finger.tpy -l /var/finger/log</li>
+
+<li>twistd -y finger.tpy --pidfile /var/run/finger.pid</li>
+
+<li>twistd -y finger.tpy --chroot /var/run</li>
+
+</ul>
+<hr />
+<em>Professor Whittlessy (Is There a Woogy In the House?, season 1) -- I use your house as an example</em>
+<h2>Writing Plugins</h2><ul>
+<li>Automatically create application configurations</li>
+
+<li>Accessible via commandline or GUI</li></ul>
+
+<h2>Writing Plugins (Example)</h2>
+<pre class="python">
+# File finger/tap.py
+from twisted.python import usage
+
+class Options(usage.Options):
+ synopsis = "Usage: mktap finger [options]"
+ optParameters = [["port", "p", 6666,"Set the port number."]]
+ longdesc = 'Finger Server'
+ users = ()
+
+ def opt_user(self, user):
+ if not '=' in user: status = "Online"
+ else: user, status = user.split('=', 1)
+ self.users += ((user, status+"\n"),)
+</pre>
+
+
+<h2>Writing Plugins (Example cont'd)</h2>
+<pre class="python">
+def updateApplication(app, config):
+ f = FingerFactory()
+ for (user, status) in config.users:
+ f.setUser(user, status)
+ app.listenTCP(int(config.opts['port']), s)
+</pre>
+<hr />
+<em>Paige (Bite Me, season 4) -- They won't join us willingly.</em>
+<h2>Writing Plugins (Example cont'd 2)</h2>
+<pre class="python">
+# File finger/plugins.tml
+register("Finger",
+ "finger.tap",
+ description="Finger Server",
+ type='tap',
+ tapname="finger")
+</pre>
+<hr />
+<em>Queen (Bite Me, season 4) -- That's what families are for.</em>
+<h2>Using mktap</h2><ul>
+<li>mktap finger --user moshez --user spiv=Offline</li>
+
+<li>twistd -f finger.tap</li>
+
+</ul>
+<hr />
+<em>Piper (Charmed and Dangerous, season 4) -- We'll use potions instead.</em>
+<h2>Delayed execution</h2><ul>
+<li>Basic interface: reactor.callLater(&lt;time&gt;, &lt;function&gt;, [&lt;arg&gt;, [&lt;arg&gt; ...]])</li>
+
+<li>reactor.callLater(10, reactor.stop)</li>
+
+<li>reactor.callLater(5, util.println, 'hello', 'world')</li>
+
+</ul>
+<hr />
+<em>Cole (Enter the Demon, season 4) -- I know, but not right now.</em>
+<h2>callLater(0,) -- An idiom</h2><ul>
+<li>Use to set up a call in next iteration of loop</li>
+
+<li>Can be used in algorithm-heavy code to let other code run</li></ul>
+
+<pre class="python">
+def calculateFact(cur, acc=1, d=None):
+ d = d or defer.Deferred()
+ if cur&lt;=1: d.callback(acc)
+ else: reactor.callLater(0, calculateFact, acc*cur, cur-1, d)
+
+calculateFact(10
+).addCallback(lambda n: (util.println(n), reactor.stop()))
+reactor.run()
+</pre>
+<hr />
+<em>Piper (Lost and Bound, season 4) -- Someone, I won't say who, has the insane notion</em>
+<h2>UNIX Domain Sockets</h2><ul>
+<li>Servers<ul><li>reactor.listenUNIX('/var/run/finger.sock', FingerWebFactory())</li>
+</ul></li>
+
+<li>Clients<ul><li>reactor.connectUNIX('/var/run/finger.sock', FingerFactory())</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Kate (Once Upon a Time, season 3) -- Fairies don't talk the same way people do.</em>
+<h2>SSL Servers</h2>
+
+<pre class="python">
+from OpenSSL import SSL
+
+class ServerContextFactory:
+
+ def getContext(self):
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file('server.pem')
+ ctx.use_privatekey_file('server.pem')
+ return ctx
+
+reactor.listenSSL(111, FingerWebFactory(), ServerContextFactory())
+</pre>
+
+<h2>SSL Clients</h2>
+
+<ul>
+<li>from twisted.internet import ssl</li>
+
+<li>reactor.connectSSL(111, 'localhost', FingerFactory(), ssl.ClientContextFactory())</li>
+</ul>
+<hr />
+<em>Natalie (Blinded By the Whitelighter, season 3) -- I mean, in private if you wouldn't mind</em>
+<h2>Running Processes</h2><ul>
+<li>A process has two outputs: stdout and stderr</li>
+
+<li>Protocol to interface with it is different</li></ul>
+
+<pre class="python">
+class Advertizer(protocol.ProcessProtocol):
+ def outReceived(self, data): print "out", `data`
+
+ def errReceived(self, data): print "error", `data`
+
+ def processEnded(self, reason): print "ended", reason
+
+reactor.spawnProcess(Advertizer(),
+ "echo", ["echo", "hello", "world"])
+</pre>
+<hr />
+<em>Prue (Coyote Piper, season 3) -- You have to know that you can talk to me</em>
+<h2>Further Reading</h2><ul>
+<li><a href="http://twistedmatrix.com/documents/">Twisted Docs</a></li>
+
+</ul>
+<hr />
+<em>Phoebe (Animal Pragmatism, season 2) -- Ooh, the girls in school are reading this.</em>
+<h2>Questions?</h2>
+<em>Piper (Something Wicca This Way Comes, season 1) -- Tell me that's not our old spirit board?</em>
+<h2>Bonus Slides</h2>
+<em>Prue (Sleuthing With the Enemy, season 3) -- All right, you start talking or we start the bonus round.</em>
+<h2>Perspective Broker</h2><ul>
+<li>Meant to be worked async</li>
+
+<li>Can transfer references or copies</li>
+
+<li>Secure (no pickles or other remote execution mechanisms)</li>
+
+<li>Lightweight (bandwidth and CPU)</li>
+
+<li>Translucent</li>
+
+</ul>
+<hr />
+<em>Paige (Charmed Again, season 4) -- I guess I just kind of feel - connected somehow.</em>
+<h2>PB Remote Control Finger (Server)</h2>
+<pre class="python">
+from twisted.spread import pb
+
+class FingerSetter(pb.Root):
+
+ def __init__(self, ff): self.ff = ff
+
+ def remote_setUser(self, name, status):
+ self.ff.setUser(name, status+"\n")
+
+ff = FingerServerFactory()
+setter = FingerSetter(ff)
+reactor.listenUNIX("/var/run/finger.control",
+ pb.BrokerFactory(setter))
+</pre>
+<hr />
+<em>Piper (Be Careful What You Witch For, season 2) -- Okay, you think you can control the power this time?</em>
+<h2>PB Remote Control Finger (Client)</h2>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import reactor
+import sys
+
+def failed(reason):
+ print "failed:", reason.value;reactor.stop()
+
+pb.getObjectAt("unix", "/var/run/finger.control", 30
+).addCallback(lambda o: o.callRemote("setUser", *sys.argv[1:3],
+).addCallbacks(lambda _: reactor.stop(), failed)
+
+reactor.run()
+</pre>
+<hr />
+<em>Leo (Be Careful What You Witch For, season 2) -- How about you just keep your arms down until you learn how to work the controls.</em>
+<h2>Perspective Broker (Trick)</h2><ul>
+<li>Add to the application something which will call reactor.stop()</li>
+
+<li>Portable (works on Windows)</li>
+
+<li>Gets around OS security limitations</li>
+
+<li>Need to add application-level security</li>
+
+<li>The docs have the answers (see 'cred')</li>
+
+</ul>
+<hr />
+<em>Piper (Lost and Bound, season 4) -- They're not good or bad by themselves, it's how we use them</em>
+<h2>Perspective Broker (Authentication)</h2><ul>
+<li>pb.cred</li>
+
+<li>Perspectives</li>
+
+<li>Can get remote user with every call<ul><li>Inherit from pb.Perpsective</li>
+
+<li>Call methods perspective_&lt;name&gt;(self, remoteUser, ...)</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Piper (She's a Man, Baby, a Man!, season 2) -- Okey-Dokey. I get the point.</em>
+
+<h2>Perspective Broker - About Large Data Streams</h2>
+
+<ul>
+
+<li>Sending large (>640kb) strings is impossible -- feature, not bug.</li>
+
+<li>It stops DoSes</li>
+
+<li>Nobody would ever need...<ul><li>JokeTooOldError</li></ul></li>
+
+<li>Use twisted.spread.utils.Pager -- sends the data in managable chunks.</li>
+
+</ul>
+
+<hr />
+<em>Piper (Womb Raider, season 4) --
+Oral tradition tales of a giant whose body served as a portal to other
+dimensions.</em>
+
+<h2>Producers and Consumers</h2><ul>
+<li>Use for things like sending a big file</li>
+
+<li>A good alternative to manually reactor.callLater(0,)-ing</li>
+
+<li>See twisted.internet.interfaces.{IProducer,IConsumer}</li>
+
+</ul>
+<hr />
+<em>Phoebe (Black as Cole, season 4) -- Apparently he feeds on the remains of other demons' victims.</em>
+<h2>Threads (callInThread)</h2><ul>
+<li>Use for long running calculations</li>
+
+<li>Use for blocking calls you can't do without</li>
+
+<li>deferred = reactor.callInThread(function, arg, arg)</li>
+
+</ul>
+<hr />
+<em>Piper (The Painted World, season 2) -- There will be consequences. There always are.</em>
+<h2>Threads (callFromThread)</h2><ul>
+<li>Use from a function running in a different thread</li>
+
+<li>Always thread safe</li>
+
+<li>Interface to non-thread-safe APIs</li>
+
+<li>reactor.callFromThread(protocol.transport.write, s)</li>
+
+</ul>
+<hr />
+<em>Phoebe (Witch Trial, season 2) -- Maybe it's still in the house. Just on different plane.</em>
+
+<h2>Using ApplicationService</h2><ul>
+<li>Keep useful data...</li>
+
+<li>...or useful volatile objects</li>
+
+<li>Support start/stop notification</li>
+
+<li>Example: process monitor</li>
+
+</ul>
+<hr />
+<em>Phoebe (Marry Go Round, season 4) -- Yeah, that's just in case you need psychic services.</em>
+
+<h2>Playing With Persistence</h2><ul>
+<li>Shutdown taps are useful</li>
+
+<li>Even if you use twistd -y</li>
+
+<li>So remember<ul><li>Classes belong in modules</li>
+
+<li>Functions belong in modules</li>
+
+<li>Modifying class attributes should be avoided</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Cole (Marry Go Round, season 4) -- That Lazerus demon is a time bomb waiting to explode</em>
+</body></html>
diff --git a/doc/historic/2003/europython/webclients.html b/doc/historic/2003/europython/webclients.html
new file mode 100644
index 0000000..8a26f71
--- /dev/null
+++ b/doc/historic/2003/europython/webclients.html
@@ -0,0 +1,482 @@
+<html><head><title>Writing Web Clients</title></head><body>
+
+<h1>Writing Web Clients</h1>
+
+<h2>Web Clients -- The Tutorial</h2><ul>
+<li>Welcome</li>
+
+<li>Gimmick -- Buffy quotes</li>
+
+</ul>
+<hr />
+<em>Anya (Family, season 5) -- Thank you for coming. We value your patronage.</em>
+<h2>What Are Web Clients?</h2><ul>
+<li>Clarification: non-interactive web clients</li>
+
+<li>Special purpose</li>
+
+<li>Often, quick and dirty hacks</li>
+
+<li>Make a web page into API</li>
+
+</ul>
+<hr />
+<em>Giles (Family, season 5) -- Could we please be a little less effusive, Anya?</em>
+<h2>What Are Web Clients Useful For?</h2><ul>
+<li>Mass download</li>
+
+<li>Periodic checking</li>
+
+<li>Automating tasks<ul><li>Make a web page more friendly</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Harmony (Family, season 5) -- Aww. You're my little lamb.</em>
+<h2>Review of Modules</h2><ul>
+<li>htmllib</li>
+
+<li>sgmllib</li>
+
+<li>httplib</li>
+
+<li>urllib</li>
+
+<li>urllib2</li>
+
+<li>urlparse</li>
+
+</ul>
+<hr />
+<em>Buffy (Family, season 5) -- Your definition of narrow is impressively wide.</em>
+<h2>Modules -- htmllib</h2><ul>
+<li>Most useful for easy filtering of images</li>
+
+<li>...or links</li>
+
+<li>Other things often easier with sgmllib</li>
+
+<li>Or with re</li>
+
+<li>Or with string manipulation</li>
+
+</ul>
+<hr />
+<em>Xander (Family, season 5) -- The answer is somewhere here.</em>
+<h2>Modules -- htmllib -- idiomatic usage</h2>
+<pre>
+# For lists
+import htmllib, formatter
+
+h = htmllib.HTMLParser(formatter.NullFormatter())
+h.feed(htmlString)
+print h.anchorlist
+</pre>
+
+<hr />
+<em>Xander (Family, season 5) -- I'm helping, I'm reading, I'm quiet.</em>
+<h2>Modules -- htmllib -- idiotmatic usage (cont'd)</h2>
+<pre>
+import htmllib, formatter
+
+class IMGFinder(htmllib.HTMLParser):
+
+ def __init__(self, *args, **kw):
+ htmllib.HTMLParser.__init__(self, *args, **kw)
+ self.ims = []
+
+ def handle_image(self, src, *args): self.ims.append(src)
+
+h = IMGFinder(formatter.NullFormatter())
+h.feed(htmlString)
+print h.ims
+</pre>
+
+<hr />
+<em>Donny (Family, season 5) -- Look what I found!</em>
+<h2>Modules -- htmllib -- base</h2><ul>
+<li>Some sites use 'base' for different relative linking</li>
+
+<li>For example, Zope does</li>
+
+<li>In above examples, 'h.base' has the base</li>
+
+</ul>
+<hr />
+<em>Dawn (Family, season 5) -- This is the source of my gladness.</em>
+<h2>Modules -- htmllib -- base (example)</h2><ul>
+<li>If the page on http://example.com/foo/bar.html has a link to '../baz.html'<ul><li>It means http://example.com/baz.html</li>
+</ul></li>
+
+<li>If the original page has base='/foo/quux'<ul><li>It means http://example.com/foo/baz.html</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Riley (Family, season 5) -- Every time I think I'm getting close to you...</em>
+<h2>Modules -- urllib/urllib2</h2><ul>
+<li>High-level interface</li>
+
+<li>Treat URLs as file-like objects</li>
+
+<li>...but still allows low-level operations</li>
+
+<li>Interface largely compatible</li>
+
+</ul>
+<hr />
+<em>Glory (Family, season 5) -- I am great and I am beautiful.</em>
+<h2>Modules -- urllib/urllib2 (cont'd)</h2><ul>
+<li>Can work through object-interface</li>
+
+<li>More flexible</li>
+
+<li>Interface no longer compatible</li>
+
+<li>urllib2 better usually</li>
+
+</ul>
+<hr />
+<em>Joyce (Ted, season 2) -- He redid my entire system.</em>
+<h2>Modules -- urllib/urllib2 (examples)</h2><ul>
+<li>urllib.urlopen("http://www.yahoo.com/").read() -&gt; contents</li>
+
+<li>urllib.urlopen("http://www.yahoo.com/").info() -&gt; headers</li>
+
+<li>Same works with urllib2</li>
+
+<li>Automatically uses environment variables for proxies</li>
+
+<li>urllib2 supports proxies with authentication</li>
+
+</ul>
+<hr />
+<em>Xander (Ted, season 2) -- Yum-my!</em>
+<h2>Digression -- HTTP Overview</h2><ul>
+<li>Request/Response</li>
+
+<li>Request is command followed by headers followed by body</li>
+
+<li>Response is error code followed by headers followed by body</li>
+
+<li>No welcome message</li>
+
+</ul>
+<hr />
+<em>Tara (Family, season 5) -- ...in terms of the karmic cycle.</em>
+<h2>Example HTTP Sessions</h2><ul>
+<li>Client</li>
+</ul>
+
+<pre>
+GET /foo/bar.html HTTP/1.0
+Host: www.example.org
+&lt;blank line&gt;
+</pre>
+
+<ul><li>Server</li></ul>
+
+<pre>
+HTTP/1.0 200 OK
+Content-Type: text/html
+
+&lt;html&gt;&lt;body&gt;lalalala&lt;/body&gt;&lt;/html&gt;
+</pre>
+
+<hr />
+<em>Giles (Family, season 5) -- And you are talking about what on earth?</em>
+<h2>Modules -- httplib</h2><ul>
+<li>Low-level interface to innards of HTTP</li>
+
+<li>Absolute control</li>
+
+<li>No abstractions</li>
+
+</ul>
+<hr />
+<em>Mr. MacLay (Family, season 5) -- We know how to control her...problem.</em>
+<h2>Modules -- httplib -- example</h2><ul>
+<li>Note: usually, the Host header is important<ul><li>Virtual hosting</li>
+</ul></li></ul>
+
+<pre>
+&gt;&gt;&gt; import httplib
+&gt;&gt;&gt; h=httplib.HTTP("moshez.org")
+&gt;&gt;&gt; h.putrequest('GET', '/')
+&gt;&gt;&gt; h.putheader('Host', 'moshez.org')
+&gt;&gt;&gt; h.endheaders()
+&gt;&gt;&gt; h.getreply()
+(200, 'OK', &lt;mimetools.Message instance at 0x81220dc&gt;)
+&gt;&gt;&gt; h.getfile().read(10)
+"&lt;HTML&gt;\n&lt;HE"
+</pre>
+<hr />
+<em>Anya (Family, season 5) -- ...and it was fun!</em>
+<h2>Modules -- urlparse</h2><ul>
+<li>urlparse.urljoin -- like os.path.join for URLs</li>
+
+<li>For path manipulation<ul><li>urlparse.urlsplit</li>
+
+<li>urlparse.urlunsplit</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Buffy (Family, season 5) -- You know what, you guys, just leave it here.</em>
+<h2>Downloading Dilbert</h2>
+<pre>
+import urllib2, re
+
+URL = 'http://www.dilbert.com/'
+f = urllib2.urlopen(URL)
+s = f.read()
+href = re.compile('&lt;a href="(/comics/.*?/dilbert.*?gif)"&gt;')
+m = href.search(value)
+f = urllib2.urlretrieve(urlparse.urljoin(URL, m.group(1)),
+ "dilbert.gif")
+</pre>
+<hr />
+<em>Tara (Family, season 5) -- That was funny if you [...] are a complete dork.</em>
+<h2>Downloading Dark Angel Transcripts</h2><ul>
+<li>Common situation of mass download</li></ul>
+
+<pre>
+import urllib2, htmllib, formatter, posixpath
+URL="http://www.darkangelfan.com/episode/"
+LINK_RE = re.compile('/trans_[0-9]+\.shtml$')
+s = urllib2.urlopen(URL).read()
+h = htmllib.HTMLParser(formatter.NullFormatter())
+h.feed(s)
+links = [urlparse.urljoin(URL, link)
+ for link in h.anchorlist if LINK_RE.search(link)]
+### -- really download --
+for link in links:
+ urllib2.urlretrieve(link, posixpath.basename(link))
+</pre>
+
+<hr />
+<em>Intern (Family, season 5) -- Yeah. That makes like five this month.</em>
+<h2>Downloading Dark Angel Transcripts (select)</h2>
+
+<pre>
+class Downloader:
+
+ def __init__(self, fin, fout):
+ self.fin, self.fout, self.fileno = fin, fout, fin.fileno
+
+ def read(self):
+ buf = self.fin.read(4096)
+ if not buf:
+ for f in [self.fout, self.fin]: f.close()
+ return 1
+ self.fout.write(buf)
+</pre>
+<hr />
+<em>Joyce (Ted, season 2) -- I've been looking for the right moment.</em>
+<h2>Downloading Dark Angel Transcripts (select, cont'd)</h2><ul>
+<li>Same code up to 'really download'</li></ul>
+
+<pre>
+downloaders = [Downloader(urllib2.urlopen(link),
+ open(posixpath.basename(link), 'wb'))
+ for link in links]
+while downloaders:
+ toRead = select.select(None, [downloaders], [], [])
+ for downloader in toRead:
+ if downloader.read():
+ downloaders.remove(downloader)
+</pre>
+<hr />
+<em>Buffy (Family, season 5) -- Tara's damn birthday is just one too many things for me to worry about.</em>
+<h2>Downloading Dark Angel Transcripts (threads)</h2><ul>
+<li>Bare bones example</li></ul>
+
+<pre>
+import threading
+
+for link in links:
+ Thread(target=urllib2.urlretrieve,
+ args=(link,posixpath.basename(link)))
+</pre>
+<hr />
+<em>Buffy (Ted, season 2) -- Sounds like fun.</em>
+<h2>Digression - twisted.web.client</h2><ul>
+<li>Part of the Twisted networking framework</li>
+
+<li>High level interface to HTTP client</li>
+
+<li>Completely asynchronous</li>
+
+<li>Reports results via callbacks</li>
+
+<li>client.getpage("http://www.yahoo.com").addCallbacks(gotResult, gotError)</li>
+
+</ul>
+<hr />
+<em>Buffy (Ted, season 2) -- You're supposed to use your powers for good!</em>
+<h2>Downloading Dark Angel Transcripts (web.client)</h2>
+<pre>
+from twisted.web import client
+from twisted.internet import import reactor, defer
+
+defer.DeferredList(
+[client.downloadPage(link, posixpath.basename(link))
+ for link in links]).addBoth(lambda _: reactor.stop())
+reactor.run()
+</pre>
+<hr />
+<em>Ted (Ted, season 2) -- You don't have to worry about anything.</em>
+<h2>HTTP Authentication</h2><ul>
+<li>Client attempts to connect</li>
+
+<li>Server sends back a 401 (please authenticate)</li>
+
+<li>Client sends same request back -- with auth tokens</li>
+
+<li>Only HTTP Basic authentication widely supported</li>
+
+<li>Client can send auth tokens on more requests automatically</li>
+
+</ul>
+<hr />
+<em>Buffy (Ted, season 2) -- Ummm... Who are these people?</em>
+<h2>HTTP Authentication - manually</h2><ul>
+<li>In HTTP, authentication is a header</li>
+
+<li>Base authentication is sending username and password</li>
+</ul>
+<pre>
+user = 'moshez'
+password = 's3kr1t'
+import httplib
+h=httplib.HTTP("localhost")
+h.putrequest('GET', '/protected/stuff.html')
+h.putheader('Authorization',
+ base64.encodestring(user+":"+password).strip())
+h.endheaders()
+h.getreply()
+print h.getfile().read()
+</pre>
+<hr />
+<em>Tara (Family, season 5) -- And, uh, these are my-my friends.</em>
+<h2>HTTP Authentication - urllib2</h2><ul>
+<li>Can read username/password from URL</li>
+
+<li>urllib2.urlopen("http://moshez:s3krit@example.com"
+ "/protected/stuff.html")</li>
+
+</ul>
+<hr />
+<em>Xander (Ted, season 2) -- I am really jinxing the hell out of us.</em>
+<h2>Further Reading</h2><ul>
+<li>htmllib docs <a href="http://www.python.org/doc/current/lib/module-htmllib.html">http://www.python.org/doc/current/lib/module-htmllib.html</a></li>
+
+<li>sgmllib docs<a href="http://www.python.org/doc/current/lib/module-sgmllib.html">http://www.python.org/doc/current/lib/module-sgmllib.html</a></li>
+
+<li>urllib docs<a href="http://www.python.org/doc/current/lib/module-urllib.html">http://www.python.org/doc/current/lib/module-urllib.html</a></li>
+
+<li>urllib2 docs<a href="http://www.python.org/doc/current/lib/module-urllib2.html">http://www.python.org/doc/current/lib/module-urllib2.html</a></li>
+
+<li>httplib docs<a href="http://www.python.org/doc/current/lib/module-httplib.html">http://www.python.org/doc/current/lib/module-httplib.html</a></li>
+
+<li>re docs<a href="http://www.python.org/doc/current/lib/module-re.html">http://www.python.org/doc/current/lib/module-re.html</a></li>
+
+<li>HTTP RFC<a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">http://www.w3.org/Protocols/rfc2616/rfc2616.html</a></li>
+
+<li>W3C HTML Page<a href="http://www.w3.org/MarkUp/">http://www.w3.org/MarkUp/</a></li>
+
+<li>Twisted<a href="http://twistedmatrix.com">http://twistedmatrix.com</a></li>
+
+</ul>
+<hr />
+<em>Willow (Ted, season 2) -- 'Book-cracker Buffy', it's kind of her nickname.</em>
+<h2>Questions?</h2>
+<em>Buffy (Family, season 5) -- I let you come, now sit down and look studious.</em>
+<h2>Bonus Slides</h2>
+<em>Tara (Family, season 5) -- You always make me feel special.</em>
+
+<h2>Cookies</h2><ul>
+<li>Carry state from one page to another</li>
+
+<li>Server sends header: Set-Cookie</li>
+
+<li>Client sends on later requests header: Cookie</li>
+
+</ul>
+<hr />
+<em>Ted (Ted, season 2) -- Who's up for dessert? I made chocolate-chip cookies!</em>
+<h2>urllib2 cookies</h2><ul>
+<li>Unfortunately, no automatic cookie jar support</li>
+
+<li>Can manually use .info() to read cookies...</li>
+
+<li>...and the Request() API to send them to the server</li>
+
+</ul>
+<hr />
+<em>Joyce (Ted, season 2) -- Mm! Buffy, you've got to try one of these!</em>
+<h2>Logging Into Advogato</h2>
+<pre>
+
+import urllib2
+
+u = urllib2.urlopen("http://advogato.org/acct/loginsub.html",
+ urllib2.urlencode({'u': 'moshez',
+ 'pass': 'not my real pass'})
+cookie = u.info()['set-cookie']
+cookie = cookie[:cookie.find(';')]
+r = Request('http://advogato.org/diary/post.html',
+ urllib2.urlencode(
+ {'entry': open('entry').read(), 'post': 'Post'}),
+ {'Cookie': cookie})
+urllib2.urlopen(r).read()
+</pre>
+
+<hr />
+<em>Anya (Family, season 5) -- I have a place in the world now.</em>
+<h2>On Being Nice - Robots</h2><ul>
+<li>Some sites don't want automatic crawlers</li>
+
+<li>It is up to you whether to play nice</li>
+
+<li>But you should know the rules before you break them</li>
+
+<li>Robots file -- at /robots.txt</li>
+
+</ul>
+<hr />
+<em>Willow (Ted, season 2) -- There were design features in that robot that pre-date...</em>
+<h2>Using robotparser</h2>
+<pre>
+import robotparser
+rp = robotparser.RobotFileParser()
+rp.set_url('http://www.example.com/robots.txt')
+rp.read()
+if not rp.can_fetch('', 'http://www.example.com/'):
+ sys.exit(1)
+</pre>
+
+<hr />
+<em>Buffy (Ted, season 2) -- Tell me you didn't keep any parts.</em>
+<h2>webchecker</h2><ul>
+<li>In the source distribution, in Tools/</li>
+
+<li>Understands robots.txt</li>
+
+<li>Can override which links gets chased</li>
+
+</ul>
+<hr />
+<em>Willow (Ted, season 2) -- What do you mean, check him out?</em>
+<h2>websucker</h2><ul>
+<li>In the source distribution, in Tools/</li>
+
+<li>Uses webchecker as a module</li>
+
+<li>Saves the pages it downloads</li>
+
+</ul>
+<hr />
+<em>Buffy (Ted, season 2) -- Find out his secrets, hack into his life.</em>
+
+</body></html>
diff --git a/doc/historic/2003/haifux/haifux.html b/doc/historic/2003/haifux/haifux.html
new file mode 100644
index 0000000..255178c
--- /dev/null
+++ b/doc/historic/2003/haifux/haifux.html
@@ -0,0 +1,2235 @@
+<html><head><title>Evolution of Finger</title></head><body>
+<h1>Evolution of Finger</h1>
+
+<h2>Refuse Connections</h2>
+
+<pre>
+from twisted.internet import reactor
+reactor.run()
+</pre>
+
+<p>Here, we just run the reactor. Nothing at all will happen,
+until we interrupt the program. It will not consume (almost)
+no CPU resources. Not very useful, perhaps -- but this
+is the skeleton inside which the Twisted program
+will grow.</p>
+
+<h2>Do Nothing</h2>
+
+<pre>
+from twisted.internet import protocol, reactor
+class FingerProtocol(protocol.Protocol):
+ pass
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>Here, we start listening on port 1079 [which is supposed to be
+a reminder that eventually, we want to run on port 79, the port
+the finger server is supposed to run on. We define a protocol which
+does not respond to any events. Thus, connections to 1079 will
+be accepted, but the input ignored.</p>
+
+<h2>Drop Connections</h2>
+
+<pre>
+from twisted.internet import protocol, reactor
+class FingerProtocol(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>Here we add to the protocol the ability to respond to the
+event of beginning a connection -- by terminating it.
+Perhaps not an interesting behaviour, but it is already
+not that far from behaving according to the letter of the
+protocol. After all, there is no requirement to send any
+data to the remote connection in the standard, is there.
+The only technical problem is that we terminate the connection
+too soon. A client which is slow enough will see his send()
+of the username result in an error.</p>
+
+<h2>Read Username, Drop Connections</h2>
+
+<pre>
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>Here we make <code>FingerProtocol</code> inherit from
+<code>LineReceiver</code>, so that we get data-based events
+on a line-by-line basis. We respond to the event of receiving
+the line with shutting down the connection. Congratulations,
+this is the first standard-compliant version of the code.
+However, usually people actually expect some data about
+users to be transmitted.</p>
+
+
+<h2>Read Username, Output Error, Drop Connections</h2>
+
+<pre>
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write("No such user\r\n")
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>Finally, a useful version. Granted, the usefulness is somewhat
+limited by the fact that this version only prints out a no such
+user message. It could be used for devestating effect in honeypots,
+of course :)</p>
+
+<h2>Output From Empty Factory</h2>
+
+<pre>
+# Read username, output from empty factory, drop connections
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write(self.factory.getUser(user)+"\r\n")
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def getUser(self, user): return "No such user"
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>The same behaviour, but finally we see what usefuleness the
+factory has: as something that does not get constructed for
+every connection, it can be in charge of the user database.
+In particular, we won't have to change the protocol if
+the user database backend changes.</p>
+
+<h2>Output from Non-empty Factory</h2>
+
+<pre>
+# Read username, output from non-empty factory, drop connections
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write(self.factory.getUser(user)+"\r\n")
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs): self.users = kwargs
+ def getUser(self, user):
+ return self.users.get(user, "No such user")
+reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
+reactor.run()
+</pre>
+
+<p>Finally, a really useful finger database. While it does not
+supply information about logged in users, it could be used to
+distribute things like office locations and internal office
+numbers. As hinted above, the factory is in charge of keeping
+the user database: note that the protocol instance has not
+changed. This is starting to look good: we really won't have
+to keep tweaking our protocol.</p>
+
+<h2>Use Deferreds</h2>
+
+<pre>
+# Read username, output from non-empty factory, drop connections
+# Use deferreds, to minimize synchronicity assumptions
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs): self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
+reactor.run()
+</pre>
+
+<p>But, here we tweak it just for the hell of it. Yes, while the
+previous version worked, it did assume the result of getUser is
+always immediately available. But what if instead of an in memory
+database, we would have to fetch result from a remote Oracle?
+Or from the web? Or, or...</p>
+
+<h2>Run 'finger' Locally</h2>
+
+<pre>
+# Read username, output from factory interfacing to OS, drop connections
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def getUser(self, user):
+ return utils.getProcessOutput("finger", [user])
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>...from running a local command? Yes, this version (safely!) runs
+finger locally with whatever arguments it is given, and returns the
+standard output. This will do exactly what the standard version
+of the finger server does -- without the need for any remote buffer
+overflows, as the networking is done safely.</p>
+
+<h2>Read Status from the Web</h2>
+
+<pre>
+# Read username, output from factory interfacing to web, drop connections
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.protocols import basic
+from twisted.web import client
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, prefix): self.prefix=prefix
+ def getUser(self, user):
+ return client.getPage(self.prefix+user)
+reactor.listenTCP(1079, FingerFactory(prefix='http://livejournal.com/~'))
+reactor.run()
+</pre>
+
+<p>The web. That invention which has infiltrated homes around the
+world finally gets through to our invention. Here we use the built-in
+Twisted web client, which also returns a deferred. Finally, we manage
+to have examples of three different database backends, which do
+not change the protocol class. In fact, we will not have to change
+the protocol again until the end of this talk: we have achieved,
+here, one truly usable class.</p>
+
+
+<h2>Use Application</h2>
+
+<pre>
+# Read username, output from non-empty factory, drop connections
+# Use deferreds, to minimize synchronicity assumptions
+# Write application. Save in 'finger.tpy'
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs): self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+application = app.Application('finger', uid=1, gid=1)
+application.listenTCP(79, FingerFactory(moshez='Happy and well'))
+</pre>
+
+<p>Up until now, we faked. We kept using port 1079, because really,
+who wants to run a finger server with root privileges? Well, the
+common solution is "privilege shedding": after binding to the network,
+become a different, less privileged user. We could have done it ourselves,
+but Twisted has a builtin way to do it. Create a snippet as above,
+defining an application object. That object will have uid and gid
+attributes. When running it (later we will see how) it will bind
+to ports, shed privileges and then run.</p>
+
+<h2>twistd</h2>
+
+<pre>
+root% twistd -ny finger.tpy # just like before
+root% twistd -y finger.tpy # daemonize, keep pid in twistd.pid
+root% twistd -y finger.tpy --pidfile=finger.pid
+root% twistd -y finger.tpy --rundir=/
+root% twistd -y finger.tpy --chroot=/var
+root% twistd -y finger.tpy -l /var/log/finger.log
+root% twistd -y finger.tpy --syslog # just log to syslog
+root% twistd -y finger.tpy --syslog --prefix=twistedfinger # use given prefix
+</pre>
+
+<p>This is how to run "Twisted Applications" -- files which define an
+'application'. twistd (TWISTed Daemonizer) does everything a daemon
+can be expected to -- shuts down stdin/stdout/stderr, disconnects
+from the terminal and can even change runtime directory, or even
+the root filesystems. In short, it does everything so the Twisted
+application developer can concentrate on writing his networking code.</p>
+
+<h2>Setting Message By Local Users</h2>
+
+<pre>
+# But let's try and fix setting away messages, shall we?
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs): self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class FingerSetterFactory(protocol.ServerFactory):
+ def __init__(self, ff): self.setUser = self.ff.users.__setitem__
+ff = FingerFactory(moshez='Happy and well')
+fsf = FingerSetterFactory(ff)
+application = app.Application('finger', uid=1, gid=1)
+application.listenTCP(79, ff)
+application.listenTCP(1079, fsf, interface='127.0.0.1')
+</pre>
+
+<p>Now that port 1079 is free, maybe we can run on it a different
+server, one which will let people set their messages. It does
+no access control, so anyone who can login to the machine can
+set any message. We assume this is the desired behaviour in
+our case. Testing it can be done by simply:
+</p>
+
+<pre>
+% nc localhost 1079
+moshez
+Giving a talk now, sorry!
+^D
+</pre>
+
+<h2>Use Services to Make Dependencies Sane</h2>
+
+<pre>
+# Fix asymmetry
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class FingerService(app.ApplicationService):
+ def __init__(self, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args)
+ self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+ def getFingerSetterFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.setUser = FingerSetterProtocol, self.users.__setitem__
+ return f
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService(application, 'finger', moshez='Happy and well')
+application.listenTCP(79, f.getFingerFactory())
+application.listenTCP(1079, f.getFingerSetterFactory(), interface='127.0.0.1')
+</pre>
+
+<p>The previous version had the setter poke at the innards of the
+finger factory. It's usually not a good idea: this version makes
+both factories symmetric by making them both look at a single
+object. Services are useful for when an object is needed which is
+not related to a specific network server. Here, we moved all responsibility
+for manufacturing factories into the service. Note that we stopped
+subclassing: the service simply puts useful methods and attributes
+inside the factories. We are getting better at protocol design:
+none of our protocol classes had to be changed, and neither will
+have to change until the end of the talk.</p>
+
+<h2>Read Status File</h2>
+
+<pre>
+# Read from file
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class FingerService(app.ApplicationService):
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, f.getFingerFactory())
+</pre>
+
+<p>This version shows how, instead of just letting users set their
+messages, we can read those from a centrally managed file. We cache
+results, and every 30 seconds we refresh it. Services are useful
+for such scheduled tasks.</p>
+
+<h2>Announce on Web, Too</h2>
+
+<pre>
+# Read from file, announce on the web!
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic
+from twisted.web import resource, server, static
+import cgi
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class FingerService(app.ApplicationService):
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('text/html',
+ '&lt;h1>%s&lt;/h1>&lt;p>%s&lt;/p>' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path, "No such user")]))))
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, f.getFingerFactory())
+application.listenTCP(80, server.Site(f.getResource()))
+</pre>
+
+<p>The same kind of service can also produce things useful for
+other protocols. For example, in twisted.web, the factory
+itself (the site) is almost never subclassed -- instead,
+it is given a resource, which represents the tree of resources
+available via URLs. That hierarchy is navigated by site,
+and overriding it dynamically is possible with getChild.</p>
+
+<h2>Announce on IRC, Too</h2>
+
+<pre>
+# Read from file, announce on the web, irc
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.web import resource, server, static
+import cgi
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class IRCReplyBot(irc.IRCClient):
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ self.factory.getUser(msg
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m: self.msg(user, m))
+class FingerService(app.ApplicationService):
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('text/html',
+ '&lt;h1>%s&lt;/h1>&lt;p>%s&lt;/p>' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path, "No such user")]))))
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser
+ return f
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, f.getFingerFactory())
+application.listenTCP(80, server.Site(f.getResource()))
+application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot'))
+</pre>
+
+<p>This is the first time there is client code. IRC clients often
+act a lot like servers: responding to events form the network.
+The reconnecting client factory will make sure that severed links
+will get re-established, with intelligent tweaked exponential
+backoff algorithms. The irc client itself is simple: the only
+real hack is getting the nickname from the factory in connectionMade.</p>
+
+
+
+<h2>Add XML-RPC Support</h2>
+
+<pre>
+# Read from file, announce on the web, irc, xml-rpc
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class IRCReplyBot(irc.IRCClient):
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ self.factory.getUser(msg
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m: self.msg(user, m))
+class FingerService(app.ApplicationService):
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('text/html',
+ '&lt;h1>%s&lt;/h1>&lt;p>%s&lt;/p>' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path, "No such user")]))))
+ x = xmlrpc.XMLRPRC()
+ x.xmlrpc_getUser = self.getUser
+ r.putChild('RPC2.0', x)
+ return r
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser
+ return f
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, f.getFingerFactory())
+application.listenTCP(80, server.Site(f.getResource()))
+application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot'))
+</pre>
+
+<p>In Twisted, XML-RPC support is handled just as though it was
+another resource. That resource will still support GET calls normally
+through render(), but that is usually left unimplemented. Note
+that it is possible to return deferreds from XML-RPC methods.
+The client, of course, will not get the answer until the deferred
+is triggered.</p>
+
+
+<h2>Write Readable Code</h2>
+
+<pre>
+# Do everything properly
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (user, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self):
+ self.service = service
+
+ def render(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ["&lt;li>&lt;a href="%s">%s&lt;/a>&lt;/li> % (user, user)
+ for user in users]
+ return '&lt;ul>'+''.join(l)+'&lt;/ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(path, self.service)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self):
+ self.user = user
+ self.service = service
+
+ def render(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '&lt;h1>%s&lt;/h1>'%self.user+'&lt;p>%s&lt;/p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLPRC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(app.ApplicationService):
+
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = UserStatusTree(self)
+ x = UserStatusXR(self)
+ r.putChild('RPC2.0', x)
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, f.getFingerFactory())
+application.listenTCP(80, server.Site(f.getResource()))
+application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot'))
+</pre>
+
+<p>The last version of the application had a lot of hacks. We avoided
+subclassing, did not support things like user listings in the web
+support, and removed all blank lines -- all in the interest of code
+which is shorter. Here we take a step back, subclass what is more
+naturally a subclass, make things which should take multiple lines
+take them, etc. This shows a much better style of developing Twisted
+applications, though the hacks in the previous stages are sometimes
+used in throw-away prototypes.</p>
+
+<h2>Write Maintainable Code</h2>
+
+<pre>
+# Do everything properly, and componentize
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+
+class IFingerService(components.Interface):
+
+ def getUser(self, user):
+ '''Return a deferred returning a string'''
+
+ def getUsers(self):
+ '''Return a deferred returning a list of strings'''
+
+class IFingerSettingService(components.Interface):
+
+ def setUser(self, user, status):
+ '''Set the user's status to something'''
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(components.Interface):
+
+ def getUser(self, user):
+ """Return a deferred returning a string""""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string""""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerFactory,
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService, IFingerService)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(components.Interface):
+
+ def setUser(self, user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerSetterFactory,
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSettingService)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (user, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(components.Interface):
+
+ '''
+ @ivar nickname
+ '''
+
+ def getUser(self, user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ __implements__ = IIRCClientFactory,
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser()
+
+components.registerAdapter(IRCClientFactoryFromService, IFingerService)
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self):
+ self.putChild('RPC2.0', UserStatusXR(self.service))
+ self.service = service
+
+ def render(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ["&lt;li>&lt;a href="%s">%s&lt;/a>&lt;/li> % (user, user)
+ for user in users]
+ return '&lt;ul>'+''.join(l)+'&lt;/ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self):
+ self.user = user
+ self.service = service
+
+ def render(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '&lt;h1>%s&lt;/h1>'%self.user+'&lt;p>%s&lt;/p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLPRC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(app.ApplicationService):
+
+ __implements__ = IFingerService,
+
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+</pre>
+
+<p>In the last version, the service class was three times longer than
+any other class, and was hard to understand. This was because it turned
+out to have multiple responsibilities. It had to know how to access
+user information, by scheduling a reread of the file ever half minute,
+but also how to display itself in a myriad of protocols. Here, we
+used the component-based architecture that Twisted provides to achieve
+a separation of concerns. All the service is responsible for, now,
+is supporting getUser/getUsers. It declares its support via the
+__implements__ keyword. Then, adapters are used to make this service
+look like an appropriate class for various things: for supplying
+a finger factory to listenTCP, for supplying a resource to site's
+constructor, and to provide an IRC client factory for connectTCP.
+All the adapters use are the methods in FingerService they are
+declared to use: getUser/getUsers. We could, of course,
+skipped the interfaces and let the configuration code use
+things like FingerFactoryFromService(f) directly. However, using
+interfaces provides the same flexibility inheritance gives: future
+subclasses can override the adapters.</p>
+
+
+
+<h2>Advantages of Latest Version</h2>
+
+<ul>
+<li>Readable -- each class is short</li>
+<li>Maintainable -- each class knows only about interfaces</li>
+<li>Dependencies between code parts are minimized</li>
+<li>Example: writing a new IFingerService is easy</li>
+</ul>
+
+<pre>
+class MemoryFingerService(app.ApplicationService):
+ __implements__ = IFingerService, IFingerSetterService
+
+ def __init__(self, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args)
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def setUser(self, user, status):
+ self.users[user] = status
+
+application = app.Application('finger', uid=1, gid=1)
+# New constructor call
+f = MemoryFingerService(application, 'finger', moshez='Happy and well')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+# New: run setter too
+application.listenTCP(1079, IFingerSetterFactory(f), interface='127.0.0.1')
+</pre>
+
+<p>Here we show just how convenient it is to implement new backends
+when we move to a component based architecture. Note that here
+we also use an interface we previously wrote, FingerSetterFactory,
+by supporting one single method. We manage to preserve the service's
+ignorance of the network.</p>
+
+<h2>Another Backend</h2>
+
+<pre>
+class LocalFingerService(app.ApplicationService):
+ __implements__ = IFingerService
+
+ def getUser(self, user):
+ return utils.getProcessOutput("finger", [user])
+
+ def getUsers(self):
+ return defer.succeed([])
+
+application = app.Application('finger', uid=1, gid=1)
+f = LocalFingerService(application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+</pre>
+
+<p>We have already wrote this, but now we get more for less work:
+the network code is completely separate from the backend.</p>
+
+<h2>Yet Another Backend: Doing the Standard Thing</h2>
+
+<pre>
+import pwd
+
+class LocalFingerService(app.ApplicationService):
+ __implements__ = IFingerService
+
+ def getUser(self, user):
+ try:
+ entry = pwd.getpwnam(user)
+ except KeyError:
+ return "No such user"
+ try:
+ f=file(os.path.join(entry[5],'.plan'))
+ except (IOError, OSError):
+ return "No such user"
+ data = f.read()
+ f.close()
+ return data
+
+ def getUsers(self):
+ return defer.succeed([])
+
+application = app.Application('finger', uid=1, gid=1)
+f = LocalFingerService(application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+</pre>
+
+<p>Not much to say about that, except to indicate that by now we
+can be churning out backends like crazy. Feel like doing a backend
+for advogato, for example? Dig out the XML-RPC client support Twisted
+has, and get to work!</p>
+
+
+<h2>Aspect Oriented Programming</h2>
+
+<ul>
+<li>This is an example...</li>
+<li>...with something actually useful...</li>
+<li>...not logging and timing.</li>
+<li>Write less code, have less dependencies!</li>
+</ul>
+
+<h2>Use Woven</h2>
+
+<pre>
+# Do everything properly, and componentize
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.web.woven import page, widget
+import cgi
+
+class IFingerService(components.Interface):
+
+ def getUser(self, user):
+ '''Return a deferred returning a string'''
+
+ def getUsers(self):
+ '''Return a deferred returning a list of strings'''
+
+class IFingerSettingService(components.Interface):
+
+ def setUser(self, user, status):
+ '''Set the user's status to something'''
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(components.Interface):
+
+ def getUser(self, user):
+ """Return a deferred returning a string""""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string""""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerFactory,
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService, IFingerService)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(components.Interface):
+
+ def setUser(self, user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerSetterFactory,
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSettingService)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (user, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(components.Interface):
+
+ '''
+ @ivar nickname
+ '''
+
+ def getUser(self, user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ __implements__ = IIRCClientFactory,
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser()
+
+components.registerAdapter(IRCClientFactoryFromService, IFingerService)
+
+
+class UsersModel(model.MethodModel):
+
+ def __init__(self, service):
+ self.service = service
+
+ def wmfactory_users(self):
+ return self.service.getUsers()
+
+components.registerAdapter(UsersModel, IFingerService)
+
+class UserStatusTree(page.Page):
+
+ template = """&lt;html>&lt;head>&lt;title>Users&lt;/title>&lt;head>&lt;body>
+ &lt;h1>Users&lt;/h1>
+ &lt;ul model="users" view="List">
+ &lt;li pattern="listItem" />&lt;a view="Link" model="."
+ href="dummy">&lt;span model="." view="Text" />&lt;/a>
+ &lt;/ul>&lt;/body>&lt;/html>"""
+
+ def initialize(self, **kwargs):
+ self.putChild('RPC2.0', UserStatusXR(self.model.service))
+
+ def getDynamicChild(self, path, request):
+ return UserStatus(user=path, service=self.model.service)
+
+components.registerAdapter(UserStatusTree, IFingerService)
+
+
+class UserStatus(page.Page):
+
+ template='''&lt;html>&lt;head>&lt;title view="Text" model="user"/>&lt;/heaD>
+ &lt;body>&lt;h1 view="Text" model="user"/>
+ &lt;p mode="status" view="Text" />
+ &lt;/body>&lt;/html>'''
+
+ def initialize(self, **kwargs):
+ self.user = kwargs['user']
+ self.service = kwargs['service']
+
+ def wmfactory_user(self):
+ return self.user
+
+ def wmfactory_status(self):
+ return self.service.getUser(self.user)
+
+class UserStatusXR(xmlrpc.XMLPRC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(app.ApplicationService):
+
+ __implements__ = IFingerService,
+
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+</pre>
+
+<p>Here we convert to using Woven, instead of manually
+constructing HTML snippets. Woven is a sophisticated web templating
+system. Its main features are to disallow any code inside the HTML,
+and transparent integration with deferred results.</p>
+
+<h2>Use Perspective Broker</h2>
+
+<pre>
+# Do everything properly, and componentize
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.web.woven import page, widget
+from twisted.spread import pb
+import cgi
+
+class IFingerService(components.Interface):
+
+ def getUser(self, user):
+ '''Return a deferred returning a string'''
+
+ def getUsers(self):
+ '''Return a deferred returning a list of strings'''
+
+class IFingerSettingService(components.Interface):
+
+ def setUser(self, user, status):
+ '''Set the user's status to something'''
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(components.Interface):
+
+ def getUser(self, user):
+ """Return a deferred returning a string""""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string""""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerFactory,
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService, IFingerService)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(components.Interface):
+
+ def setUser(self, user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerSetterFactory,
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSettingService)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (user, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(components.Interface):
+
+ '''
+ @ivar nickname
+ '''
+
+ def getUser(self, user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ __implements__ = IIRCClientFactory,
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser()
+
+components.registerAdapter(IRCClientFactoryFromService, IFingerService)
+
+
+class UsersModel(model.MethodModel):
+
+ def __init__(self, service):
+ self.service = service
+
+ def wmfactory_users(self):
+ return self.service.getUsers()
+
+components.registerAdapter(UsersModel, IFingerService)
+
+class UserStatusTree(page.Page):
+
+ template = """&lt;html>&lt;head>&lt;title>Users&lt;/title>&lt;head>&lt;body>
+ &lt;h1>Users&lt;/h1>
+ &lt;ul model="users" view="List">
+ &lt;li pattern="listItem" />&lt;a view="Link" model="."
+ href="dummy">&lt;span model="." view="Text" />&lt;/a>
+ &lt;/ul>&lt;/body>&lt;/html>"""
+
+ def initialize(self, **kwargs):
+ self.putChild('RPC2.0', UserStatusXR(self.model.service))
+
+ def getDynamicChild(self, path, request):
+ return UserStatus(user=path, service=self.model.service)
+
+components.registerAdapter(UserStatusTree, IFingerService)
+
+
+class UserStatus(page.Page):
+
+ template='''&lt;html>&lt;head>&lt&lt;title view="Text" model="user"/>&lt;/heaD>
+ &lt;body>&lt;h1 view="Text" model="user"/>
+ &lt;p mode="status" view="Text" />
+ &lt;/body>&lt;/html>'''
+
+ def initialize(self, **kwargs):
+ self.user = kwargs['user']
+ self.service = kwargs['service']
+
+ def wmfactory_user(self):
+ return self.user
+
+ def wmfactory_status(self):
+ return self.service.getUser(self.user)
+
+class UserStatusXR(xmlrpc.XMLPRC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class IPerspectiveFinger(components.Interface):
+
+ def remote_getUser(self, username):
+ """return a user's status"""
+
+ def remote_getUsers(self):
+ """return a user's status"""
+
+
+class PerspectiveFingerFromService(pb.Root):
+
+ __implements__ = IPerspectiveFinger,
+
+ def __init__(self, service):
+ self.service = service
+
+ def remote_getUser(self, username):
+ return self.service.getUser(username)
+
+ def remote_getUsers(self):
+ return self.service.getUsers()
+
+components.registerAdapter(PerspectiveFingerFromService, IFingerService)
+
+
+class FingerService(app.ApplicationService):
+
+ __implements__ = IFingerService,
+
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f))
+</pre>
+
+<p>We add support for perspective broker, Twisted's native remote
+object protocol. Now, Twisted clients will not have to go through
+XML-RPCish contortions to get information about users.</p>
+
+<h2>Support HTTPS</h2>
+
+<pre>
+# Do everything properly, and componentize
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.web.woven import page, widget
+from twisted.spread import pb
+from OpenSSL import SSL
+import cgi
+
+class IFingerService(components.Interface):
+
+ def getUser(self, user):
+ '''Return a deferred returning a string'''
+
+ def getUsers(self):
+ '''Return a deferred returning a list of strings'''
+
+class IFingerSettingService(components.Interface):
+
+ def setUser(self, user, status):
+ '''Set the user's status to something'''
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(components.Interface):
+
+ def getUser(self, user):
+ """Return a deferred returning a string""""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string""""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerFactory,
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService, IFingerService)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(components.Interface):
+
+ def setUser(self, user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerSetterFactory,
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSettingService)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (user, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(components.Interface):
+
+ '''
+ @ivar nickname
+ '''
+
+ def getUser(self, user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ __implements__ = IIRCClientFactory,
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser()
+
+components.registerAdapter(IRCClientFactoryFromService, IFingerService)
+
+
+class UsersModel(model.MethodModel):
+
+ def __init__(self, service):
+ self.service = service
+
+ def wmfactory_users(self):
+ return self.service.getUsers()
+
+components.registerAdapter(UsersModel, IFingerService)
+
+class UserStatusTree(page.Page):
+
+ template = """&lt;html>&lt;head>&lt;title>Users&lt;/title>&lt;head>&lt;body>
+ &lt;h1>Users&lt;/h1>
+ &lt;ul model="users" view="List">
+ &lt;li pattern="listItem" />&lt;a view="Link" model="."
+ href="dummy">&lt;span model="." view="Text" />&lt;/a>
+ &lt;/ul>&lt;/body>&lt;/html>"""
+
+ def initialize(self, **kwargs):
+ self.putChild('RPC2.0', UserStatusXR(self.model.service))
+
+ def getDynamicChild(self, path, request):
+ return UserStatus(user=path, service=self.model.service)
+
+components.registerAdapter(UserStatusTree, IFingerService)
+
+class UserStatus(page.Page):
+
+ template='''&lt;html>&lt;head>&lt;title view="Text" model="user"/>&lt;/heaD>
+ &lt;body>&lt;h1 view="Text" model="user"/>
+ &lt;p mode="status" view="Text" />
+ &lt;/body>&lt;/html>'''
+
+ def initialize(self, **kwargs):
+ self.user = kwargs['user']
+ self.service = kwargs['service']
+
+ def wmfactory_user(self):
+ return self.user
+
+ def wmfactory_status(self):
+ return self.service.getUser(self.user)
+
+class UserStatusXR(xmlrpc.XMLPRC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class IPerspectiveFinger(components.Interface):
+
+ def remote_getUser(self, username):
+ """return a user's status"""
+
+ def remote_getUsers(self):
+ """return a user's status"""
+
+
+class PerspectiveFingerFromService(pb.Root):
+
+ __implements__ = IPerspectiveFinger,
+
+ def __init__(self, service):
+ self.service = service
+
+ def remote_getUser(self, username):
+ return self.service.getUser(username)
+
+ def remote_getUsers(self):
+ return self.service.getUsers()
+
+components.registerAdapter(PerspectiveFingerFromService, IFingerService)
+
+
+class FingerService(app.ApplicationService):
+
+ __implements__ = IFingerService,
+
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+class ServerContextFactory:
+
+ def getContext(self):
+ """Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'."""
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file('server.pem')
+ ctx.use_privatekey_file('server.pem')
+ return ctx
+
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+site = server.Site(resource.IResource(f))
+application.listenTCP(80, site)
+application.listenSSL(443, site, ServerContextFactory())
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f))
+</pre>
+
+<p>All we need to do to code an HTTPS site is just write a context
+factory (in this case, which loads the certificate from a certain file)
+and then use the listenSSL method. Note that one factory (in this
+case, a site) can listen on multiple ports with multiple protocols.</p>
+
+<h2>Finger Proxy</h2>
+
+<pre>
+class FingerClient(protocol.Protocol):
+
+ def connectionMade(self):
+ self.transport.write(self.factory.user+"\r\n")
+ self.buf = []
+
+ def dataReceived(self, data):
+ self.buf.append(data)
+
+ def connectionLost(self):
+ self.factory.gotData(''.join(self.buf))
+
+
+class FingerClientFactory(protocol.ClientFactory):
+
+ protocol = FingerClient
+
+ def __init__(self, user):
+ self.user = user
+ self.d = defer.Deferred()
+
+ def clientConnectionFailed(self, _, reason):
+ self.d.errback(reason)
+
+ def gotData(self, data):
+ self.d.callback(data)
+
+
+def finger(user, host, port=79):
+ f = FingerClientFactory(user)
+ reactor.connectTCP(host, port, f)
+ return f.d
+
+class ProxyFingerService(app.ApplicationService):
+ __implements__ = IFingerService
+
+ def getUser(self, user):
+ user, host = user.split('@', 1)
+ ret = finger(user, host)
+ ret.addErrback(lambda _: "Could not connect to remote host")
+ return ret
+
+ def getUsers(self):
+ return defer.succeed([])
+
+application = app.Application('finger', uid=1, gid=1)
+f = ProxyFingerService(application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+</pre>
+
+<p>Writing new clients with Twisted is much like writing new servers.
+We implement the protocol, which just gathers up all the data, and
+give it to the factory. The factory keeps a deferred which is triggered
+if the connection either fails or succeeds. When we use the client,
+we first make sure the deferred will never fail, by producing a message
+in that case. Implementing a wrapper around client which just returns
+the deferred is a common pattern. While being less flexible than
+using the factory directly, it is also more convenient.</p>
+
+<h2>Organization</h2>
+
+<ul>
+<li>Code belongs in modules: everything above the <code>application=</code>
+ line.</li>
+<li>Templates belong in separate files. The templateFile attribute can be
+ used to indicate the file.</li>
+<li>The templateDirectory attribute will be used to indicate where to look
+ for the files.</li>
+</ul>
+
+<pre>
+from twisted.internet import app
+from finger import FingerService, IIRCclient, ServerContextFactory, \
+ IFingerFactory, IPerspectiveFinger
+from twisted.web import resource, server
+from twisted.spread import pb
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+r = resource.IResource(f)
+r.templateDirectory = '/usr/share/finger/templates/'
+site = server.Site(r)
+application.listenTCP(80, site)
+application.listenSSL(443, site, ServerContextFactory())
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f))
+</pre>
+
+<ul>
+<li>Seperaration between: code (module), configuration (file above),
+ presentation (templates), contents (/etc/users), deployment (twistd)</li>
+<li>Examples, early prototypes don't need that.</li>
+<li>But when writing correctly, easy to do!</li>
+</ul>
+
+<h2>Easy Configuration</h2>
+
+<p>We can also supply easy configuration for common cases</p>
+
+<pre>
+# in finger.py moudle
+def updateApplication(app, **kwargs):
+ f = FingerService(kwargs['users'], application, 'finger')
+ application.listenTCP(79, IFingerFactory(f))
+ r = resource.IResource(f)
+ r.templateDirectory = kwargs['templates']
+ site = server.Site(r)
+ app.listenTCP(80, site)
+ if kwargs.get('ssl'):
+ app.listenSSL(443, site, ServerContextFactory())
+ if kwargs.has_key('ircnick'):
+ i = IIRCClientFactory(f)
+ i.nickname = kwargs['ircnick']
+ ircServer = kwargs['ircserver']
+ application.connectTCP(ircserver, 6667, i)
+ if kwargs.has_key('pbport'):
+ application.listenTCP(int(kwargs['pbport']),
+ pb.BrokerFactory(IPerspectiveFinger(f))
+</pre>
+
+<p>And we can write simpler files now:</p>
+
+<pre>
+# simple-finger.tpy
+from twisted.internet import app
+import finger
+
+application = app.Application('finger', uid=1, gid=1)
+finger.updateApplication(application,
+ users='/etc/users',
+ templatesDirectory='/usr/share/finger/templates',
+ ssl=1,
+ ircnick='fingerbot',
+ ircserver='irc.freenode.net',
+ pbport=8889
+)
+</pre>
+
+<p>Note: the finger <em>user</em> still has ultimate power: he can use
+updateApplication, or he can use the lower-level interface if he has
+specific needs (maybe an ircserver on some other port? maybe we
+want the non-ssl webserver to listen only locally? etc. etc.)
+This is an important design principle: never force a layer of abstraction:
+allow usage of layers of abstractions.</p>
+
+<p>The pasta theory of design:</p>
+
+<ul>
+<li>Spaghetti: each piece of code interacts with every other piece of
+ code [can be implemented with GOTO, functions, objects]</li>
+<li>Lasagna: code has carefully designed layers. Each layer is, in
+ theory independent. However low-level layers usually cannot be
+ used easily, and high-level layers depend on low-level layers.</li>
+<li>Raviolli: each part of the code is useful by itself. There is a thin
+ layer of interfaces between various parts [the sauce]. Each part
+ can be usefully be used elsewhere.</li>
+<li>...but sometimes, the user just wants to order "Raviolli", so one
+ coarse-grain easily definable layer of abstraction on top of it all
+ can be useful.</li>
+</ul>
+
+<h2>Plugins</h2>
+
+<p>So far, the user had to be somewhat of a programmer to use this.
+Maybe we can eliminate even that? Move old code to
+"finger/service.py", put empty "__init__.py" and...</p>
+
+<pre>
+# finger/tap.py
+from twisted.python import usage
+from finger import service
+
+class Options(usage.Options):
+
+ optParams = [
+ ['users', 'u', '/etc/users'],
+ ['templatesDirectory', 't', '/usr/share/finger/templates'],
+ ['ircnick', 'n', 'fingerbot'],
+ ['ircserver', None, 'irc.freenode.net'],
+ ['pbport', 'p', 8889],
+ ]
+
+ optFlags = [['ssl', 's']]
+
+def updateApplication(app, config):
+ service.updateApplication(app, **config)
+</pre>
+
+<p>And register it all:</p>
+
+<pre>
+#finger/plugins.tml
+register('Finger', 'finger.tap', type='tap', tapname='finger')
+</pre>
+
+<p>And now, the following works</p>
+
+<pre>
+% mktap finger --users=/usr/local/etc/users --ircnick=moshez-finger
+% sudo twistd -f finger.tap
+</pre>
+
+<h2>OS Integration</h2>
+
+<p>If we already have the "finger" package installed, we can achieve
+easy integration:</p>
+
+<p>on Debian--</p>
+
+<pre>
+% tap2deb --unsigned -m "Foo <foo@example.com>" --type=python finger.tpy
+% sudo dpkg -i .build/*.deb
+</pre>
+
+<p>On Red Hat [or Mandrake]</p>
+
+<pre>
+% tap2rpm --type=python finger.tpy #[maybe other options needed]
+% sudo rpm -i .build/*.rpm
+</pre>
+
+<p>Will properly register configuration files, init.d sripts, etc. etc.</p>
+
+<p>If it doesn't work on your favourite OS: patches accepted!</p>
+
+<h2>Summary</h2>
+
+<ul>
+<li>Twisted is asynchronous</li>
+<li>Twisted has implementations of every useful protocol</li>
+<li>In Twisted, implementing new protocols is easy [we just did three]</li>
+<li>In Twisted, achieving tight integration of servers and clients
+ is easy.</li>
+<li>In Twisted, achieving high code usability is easy.</li>
+<li>Ease of use of Twisted follows, in a big part, from that of Python.</li>
+<li>Bonus: No buffer overflows. Ever. No matter what.</li>
+</ul>
+
+<h2>Motto</h2>
+
+<ul>
+<li>"Twisted is not about forcing. It's about mocking you when you use
+ the technology in suboptimal ways."</li>
+<li>You're not forced to use anything except the reactor...</li>
+<li>...not the protocol implementations...</li>
+<li>...not application...</li>
+<li>...not services...</li>
+<li>...not components...</li>
+<li>...not woven...</li>
+<li>...not perspective broker...</li>
+<li>...etc.</li>
+<li>But you should!</li>
+<li>Reinventing the wheel is not a good idea, especially if you form
+ some vaguely squarish lump of glue and poison and try and attach
+ it to your car.</li>
+<li>The Twisted team solved many of the problems you are likely to come
+ across...</li>
+<li>...several times...</li>
+<li>...getting it right the nth time.</li>
+</ul>
+
+
+</body></html>
diff --git a/doc/historic/2003/haifux/notes.html b/doc/historic/2003/haifux/notes.html
new file mode 100644
index 0000000..c35afa8
--- /dev/null
+++ b/doc/historic/2003/haifux/notes.html
@@ -0,0 +1,60 @@
+<html><head><title>Notes</title></head><body>
+<h1>Notes</h1>
+
+<p>[translated roughly from Hebrew]</p>
+
+<h2>Introduction</h2>
+
+<ul>
+<li>Name: Moshe Zadka</li>
+<li>Twisted developer [Debian, Python]</li>
+<li>Not:<ul>
+<li>XML talk (XML is: standarised, flexibl, internationalized)</li>
+<li>Gettysburg in Power Point</li>
+<li>Touching lots of things briefly</li>
+</ul></li>
+<ul>How to do more than one thing at once?<ul>
+<li>Fork (Apache)</li>
+<li>Thread (AOLServer)</li>
+<li>Cheat (GUI programs)</li>
+</ul></li>
+<li>Main loop calling our code.</li>
+<li>Let's develop a network program!</li>
+</ul>
+
+<h2>Discussion</h2>
+
+<ul>
+<li>What is blocking?
+<ul>
+<li>There is a UNIX concept of blocking...</li>
+<li>...which is not really relevant.</li>
+<li>Connecting to an accepting UNIX domain socket is blocking...</li>
+<li>...reading a file from NFS is not.</li>
+</ul></li>
+<li>Wait a minute: why is that interesting?<ul>
+<li>GUI -- humans (0.1s-1s)</li>
+<li>Network: connections might get refused</li>
+</ul></li>
+<li>Typical scenario: listen buffer 5, 1e6 connections/day --
+ don't dawdle for more than 0.08s</li>
+<li>These are the numbers that matter!</li>
+<li>Useful criterion: blocking==takes more than 0.01s on normal load.</li>
+<li>Depends on hardware, etc.</li>
+<li>Real world :(</li>
+<li>But a useful rule of thumb when coding.</li>
+<li>Trick: reactor.callLater(0,)</li>
+<li>Continuation-passing-style, tail-call-optimization</li>
+<li>But not pure -- not optimal</li>
+</ul>
+
+<h2>References</h2>
+
+<ul>
+<li>Plonk</li>
+<li>twistedmatrix.com</li>
+<li>mailing list</li>
+<li>irc -- #twisted</li>
+</ul>
+
+</body></html>
diff --git a/doc/historic/2003/pycon/applications/applications b/doc/historic/2003/pycon/applications/applications
new file mode 100755
index 0000000..a6c18a2
--- /dev/null
+++ b/doc/historic/2003/pycon/applications/applications
@@ -0,0 +1,257 @@
+#!/usr/bin/python
+from slides import Slide, Bullet, SubBullet, URL, Image, PRE
+from twslides import Lecture
+
+class PythonSource:
+ def __init__(self, content):
+ self.content = content
+ def toHTML(self):
+ return '<pre class="python">%s</pre>' % (self.content,)
+
+class Raw:
+ def __init__(self, content):
+ self.content = content
+ def toHTML(self):
+ return self.content + '\n'
+
+lecture = Lecture(
+ "Applications of Twisted",
+ Slide("Twisted.names",
+ Bullet("Domain Name Server", SubBullet(
+ Bullet("Authoritative"),
+ Bullet("Caching"),
+ Bullet("Other!"),
+ )),
+ Bullet("Domain Name Client"),
+ ),
+ Slide("Mostly Functional",
+ Bullet("All common records support; 22 supported total", SubBullet(
+ Bullet("A, NS, CNAME, SOA, PTR, HINFO, MX, TXT"),
+ Bullet("IPv6 records AAAA and A6"),
+ )),
+ Bullet("No DNSSEC support"),
+ Bullet("Server and Client functionality"),
+ ),
+ Slide("Rapidly Developed",
+ Bullet("One month initial development period", SubBullet(
+ Bullet("Python is good for rapid development"),
+ Bullet("Twisted handles all the boring network details"),
+ )),
+ Bullet("Easily extended", SubBullet(
+ Bullet("Doesn't choke on unrecognized record types"),
+ Bullet("Support for a new record type can be added in "
+ "just a few minutes"),
+ Bullet(PythonSource("""\
+from twisted.protocols import dns
+
+class Record_A:
+ __implements__ = (dns.IEncodable,)
+ TYPE = dns.QUERY_TYPES['A'] = 1
+
+ def __init__(self, address = '0.0.0.0'):
+ self.address = socket.inet_aton(address)
+
+ def encode(self, strio, compDict = None):
+ strio.write(self.address)
+
+ def decode(self, strio, length = None):
+ self.address = readPrecisely(strio, 4)
+"""
+ )),
+ )),
+ ),
+ Slide("Easily Configured",
+ Bullet("BIND zonefile syntax"),
+ Bullet("Python source", SubBullet(
+ Bullet(PythonSource("""\
+zone = [
+ AAAA('intarweb.us', '3ffe:b80:1886:1::1'),
+ SRV('_http._tcp.www.intarweb.us', 0, 0, 8080, 'intarweb.us'),
+ MX('intarweb.us', 10, 'mail.intarweb.us')
+]
+"""
+ )),
+ )),
+ Bullet("Twisted's mktap and twistd tools", SubBullet(
+ PRE("mktap dns --pyzone a.domain.zonefile --recursive --cache"),
+ PRE("twistd -f dns.tap"),
+ )),
+ ),
+ Slide("Client API",
+ Bullet("Asynchronous", SubBullet(
+ Bullet("All lookup functions return Deferred objects"),
+ Bullet(PythonSource(
+"""\
+import random
+from twisted.names import client
+
+def addressFor(service, protocol, domain):
+ d = client.theResolver.lookupService(
+ '_%s._%s.%s' % (service, protocol, domain)
+ )
+
+ def grabPayload((answers, authority, additional)):
+ return [r.payload for r in answers]
+
+ def randomAnswer(results):
+ if len(results) == 1 and results[0] == '.':
+ raise RuntimeException, "No service records found"
+ return random.choice(results)
+
+ return d.addCallback(grabPayload).addCallback(randomAnswer)
+"""
+ )),
+ )),
+ ),
+ Slide("Uses",
+ Bullet("Service Records", SubBullet(
+ Bullet("Potential to simplify user experience"),
+ Bullet("Not widely accessible"),
+ Bullet("Names' client API makes accessing them trivial"),
+ )),
+ ),
+ Slide("Pynfo: A Network Information 'Bot",
+ Bullet("A 'bot with the goal of integrating access to miscellaneous data inputs"),
+ ),
+ Slide("Architecture",
+ Bullet("Factories", SubBullet(
+ Bullet("Takes care of connecting to different services"),
+ Bullet("Acts as a central storage for shared data"),
+ Bullet("Currently only IRC is supported"),
+ Bullet("Planned support for web, IM, and PB interfaces")
+ )),
+ Bullet("Protocols", SubBullet(
+ Bullet("Created by Factories"),
+ Bullet("Handles all service-specific interaction"),
+ Bullet("Refers back to the factory for shared data"),
+ Bullet("Current support for IRC only")
+ )),
+ Image("pynfo-chart.png"),
+ Bullet("Separation of Factory and Protocols", SubBullet(
+ Bullet("Per-protocol data separate, per-robot data shared"),
+ Bullet("Protocols destroyed on disconnect, factory manage reconnecting"),
+ )),
+ ),
+ Slide("Plugins",
+ Bullet("Plugins are modules plus some metadata", SubBullet(
+ Bullet("A plugin name"),
+ Bullet("A plugin description"),
+ Bullet("A plugin type"),
+ Bullet("Any other data appropriate for the type"),
+ )),
+ Bullet("Initialization / Finalization hooks"),
+ Bullet("Input filtering, for behaviors like ignore"),
+ Bullet("An example", SubBullet(
+ Bullet(PythonSource("""
+from twisted.names import client
+def info_LOOKUP(bot, user, channel, query):
+ def tellUserResponse((ans, auth, add)):
+ bot.reply(user, "%s: %s" % (query, [str(a.payload) for a in ans]))
+
+ def tellUserError(failure):
+ bot.reply(user, "Host lookup failed.")
+
+ client.lookupAddress(query).addCallbacks(
+ tellUserResponse, tellUserError
+ )
+"""
+ )),
+ )),
+ ),
+ Slide("But where do they come from?",
+ Bullet("Twisted's plugin module", SubBullet(
+ Bullet(PythonSource("""\
+from twisted.python import plugin
+class InfoBotFactory:
+ ...
+ def loadPlugins(self):
+ ...
+ p = plugin.getPlugIns('infobot')
+ ....
+"""
+ )),
+ )),
+ ),
+ Slide("Persistence",
+ Bullet("Addresses to connect to and protocols to use"),
+ Bullet("Administrators, passwords, keys"),
+ Bullet("Connection statistics"),
+ Bullet("Plugins can store objects for later retrieval")
+ ),
+ Slide("Components",
+ Bullet("Shared and pluggable behavior is implemented as Adapters for Interfaces", SubBullet(
+ Bullet("IScheduler, IStorage, IAuthenticator"),
+ )),
+ Bullet("Plugins can register their own adapters for the factory", SubBullet(
+ Bullet("Gracefully add new capabilities without __class__ hacks"),
+ Bullet("Share capabilities with other plugins"),
+ Bullet("Avoids namespace collisions"),
+ )),
+ ),
+ Slide("Interaction",
+ Bullet("Commands and responses are issued through normal protocol actions"),
+ Bullet('Three levels of command "security"'),
+ Bullet("Access to some commands is unrestricted",
+ Raw("<code>"),
+ SubBullet("<exarkun> pyn: networks"),
+ SubBullet("<pyn> Connected to: oftc -> ('irc.oftc.net', 6667), fn -> ('irc.freenode.net', 6667)"),
+ Raw("</code>"),
+ ),
+ Bullet("Access to others is granted via an ACL",
+ Raw("<code>"),
+ SubBullet("<exarkun> pyn: rebuild"),
+ SubBullet("<pyn> You aren't allowed to do that."),
+ Raw("</code>"),
+ ),
+ Bullet("Still further access is granted by the possession of a secret key",
+ Raw("<code>"),
+ SubBullet("<exarkun> pyn: spill self.transport.getHost()"),
+ SubBullet("<pyn> Command queued. Challenge: BBKSkYCRCGQETx4kTmceUg==%"),
+ SubBullet("<exarkun> pyn: respond 2lwcgSVnJPzrW6Yvq7sg+g==%"),
+ SubBullet("<pyn> ('INET', '192.168.123.137', 45539)"),
+ Raw("</code>"),
+ )
+ ),
+ Slide("Network Bridging",
+ Bullet("Pass messages between networks",
+ Raw("<code>"),
+ SubBullet("<pyn> Yosomono (~fake@hostmask) has joined on efnet"),
+ SubBullet("<pyn> <Yosomono@efnet> hello"),
+ Raw("</code>"),
+ ),
+ Bullet("Requesting user information across networks",
+ Raw("<code>"),
+ SubBullet("<exarkun> pyn: whois Yosomono@efnet"),
+ SubBullet("<pyn> Hostmask: Yosomono!fake@hostmask"),
+ SubBullet("<pyn> Channels: @#python"),
+ Raw("</code>"),
+ ),
+ ),
+ Slide("Conversation Logging",
+ Bullet("The 'conversation' command", SubBullet(
+ Raw("<code>"),
+ SubBullet("<exar[con]> pyn: conversation begin PyCon example conversation"),
+ SubBullet("<pyn> Beginning tagged conversation 'PyCon example conversation'."),
+ SubBullet("<exar[con]> Hello, PyCon"),
+ SubBullet("<exar[con]> Enjoy the example!"),
+ SubBullet("<exar[con]> pyn: conversation end PyCon example conversation"),
+ SubBullet("<pyn> Ended tagged conversation 'PyCon example conversation'."),
+ Raw("</code>"),
+ )),
+ Bullet("Web interface", SubBullet(
+ URL("http://c.intarweb.us:8008/%23tanstaafl/PyCon%20example%20conversation"),
+ )),
+ Bullet("Search previous logs and add conversation tags"),
+ ),
+ Slide("Various other plugins",
+ Bullet("PyPI monitor and querying"),
+ Bullet("Network specific operations - IRC operator module"),
+ Bullet("Freshmeat and Google querying"),
+ Bullet("Link shortener"),
+ Bullet("PyMetar plugin"),
+ Bullet("Manhole"),
+ ),
+ Slide("Questions?"),
+)
+
+lecture.renderHTML(".", "applications-%d.html", css="stylesheet.css")
diff --git a/doc/historic/2003/pycon/applications/applications.html b/doc/historic/2003/pycon/applications/applications.html
new file mode 100644
index 0000000..f357d7f
--- /dev/null
+++ b/doc/historic/2003/pycon/applications/applications.html
@@ -0,0 +1,343 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html>
+ <head>
+ <title>Applications of the Twisted Framework</title>
+ <link href="stylesheet.css" type="text/css" rel="stylesheet" />
+ </head>
+ <body>
+ <h1>Applications of the Twisted Framework</h1>
+ <p>Jp Calderone</p>
+ <p>exarkun@twistedmatrix.com</p>
+
+ <h2>ABSTRACT</h2>
+
+ <p>Two projects developed using the Twisted framework are described;
+ one, Twisted.names, which is included as part of the Twisted
+ distribution, a domain name server and client API, and one, Pynfo, which
+ is packaged separately, a network information robot.</p>
+
+ <h2>Twisted (dot) Names</h2>
+
+ <h3>Motivation</h3>
+ <p>The field of domain name servers is well explored and numerous
+ strong, widely-deployed implementations of the protocol exist. DNSSEC,
+ IPv6, service location, geographical location, and many of the other DNS
+ extension proposals all have high quality support in BIND, djbdns,
+ maradns, and others. From a client's perspective, though, the landscape
+ looks a little different. APIs to perform arbitrary domain name lookups
+ are sparse. In contrast, Twisted.names presents a richly featured,
+ asynchronous client API.</p>
+
+ <h3>Names Server</h3>
+ <p><b>Names</b> is capable of operating as a fully functional domain
+ name server. It implements caching, recursive lookups, and can act as
+ the authority for an arbitrary number of domains. It is not, however, a
+ finely tuned performance machine. Responding to queries can take about
+ twice the time other domain name servers might need. It has not been
+ investigated whether this is a design limitation or merely the result of
+ an unoptimized implementation.</p>
+
+ <h3>Names Client</h3>
+ <p>As a client, <b>Names</b> provides an easy interface to every type of
+ record supported by. Looking up the MX records for a host, for example,
+ might look like this:</p>
+
+ <pre class="python">
+ def _cbMailExchange(results):
+ # Callback for MX query
+ answers = results[0]
+ print 'Mail Exchange is: ', answers
+
+ def _ebMailExchange(failure):
+ # Error callback for MX query
+ print 'Lookup failed:'
+ failure.printTraceback()
+
+ from twisted.names import client
+ d = client.lookupMailExchange('example-domain.com')
+ d.addCallbacks(_cbMailExchange, _ebMailExchange)
+ </pre>
+
+ <p>Looking up other record types is as simple as calling a different
+ <code>lookup*</code> function.</p>
+
+ <h3>Implementation</h3>
+
+ <p>As with most network software written using Twisted, the first step
+ in developing <b>Names</b> was to write the protocol support. In this
+ case, the protocol was DNS, and support was partially implemented.
+ However, it attempted to merge support for both UDP and TCP, and ended
+ up with less than optimal results. Much of this code was discarded,
+ though some of the lowest level encoding and decoding code worked well
+ and was re-used.</p>
+
+ <p>With the two protocol classes, DNSDatagramProtocol and DNSProtocol
+ (the TCP version) implemented, the next step was to write classes which
+ created the proper behavior for a domain name server. This logic was
+ put in the <code>twisted.names.server.DNSServerFactory</code> class,
+ which in turn relies on several different kind of <code>Resolver</code>s
+ to find the appropriate response to queries it receives from the
+ protocol instance.</p>
+
+ <p>The chain of execution, then, is this: a packet is received by the
+ protocol object (a <code>DNSDatagramProtocol</code> or
+ <code>DNSProtocol</code> instance); the packet is decoded by
+ <code>twisted.protocols.dns.RRHeader</code> in cooperation with one of
+ the record classes (<code>twisted.protocols.dns.Record_A</code> for
+ example); the decoded <code>twisted.protocols.dns.Query</code> object is
+ passed up to the <code>twisted.names.server.DNSServerFactory</code>,
+ which determines the query type and invokes the appropriate lookup
+ method on each of its resolver objects in turn; if an answer is found,
+ it is passed back down to the protocol instance (otherwise the
+ appropriate bit for an error condition is set), where it is encoded and
+ transmitted back to the client.</p>
+
+ <p>There are four kinds of resolvers in the current implementation. The
+ first three are authorities, caches, and recursive resolvers. They are
+ generally queried, in this order, using the fourth resolver, the "chain"
+ resolver, which simply queries the resolvers it knows about, moving on
+ to the next when any given resolver fails to produce a response, and
+ generating the proper exception when the last resolver has failed.</p>
+
+ <h3>Shortcomings</h3>
+
+ <p>There are several aspects of Twisted Names that might preclude its
+ use in "production" software. These issues stem mainly from its
+ immaturity, it being less than six months old at the writing of this
+ paper.</p>
+
+ <ul>
+ <li><p>Possibly of foremost interest to those who might use it in a
+ high-load environment, it has somewhat poor runtime performance
+ characteristics. One potential reason for this is the extensive use of
+ exceptions to signal the relatively common case of a resolver lookup
+ failing. Solutions to this problem are apparent, but an implementation
+ change has not been attempted. Until this area of its development is
+ more fully examined, it will likely not be of use in anything other than
+ for low- to mid-load tasks, or with more hardware available to it than
+ might seem reasonable.</p></li>
+
+ <li>No attempt has been made to implement DNSSEC.</li>
+
+ <li>Certain areas of the server remain out of compliance with the
+ standardized RFCs, occasionally causing undesirable behavior when
+ interacting with clients. This most frequently manifests itself as a
+ lookup which fails the first time and succeeds on subsequent attempts.
+ It is not believed that these represent architectural flaws, only small
+ oversights in areas such as the "additional processing" sections of the
+ current authority resolver implementations.</li>
+ </ul>
+ <h2>Pynfo</h2>
+
+ <h3>Motivation</h3>
+ <p>Pynfo was originally begun as a learning project to become acquainted
+ with the Twisted framework. After a brief initial development period
+ and an extended period of non-development, Pynfo was picked up again to
+ serve as a replacement for several existing robots, each with fragile
+ code bases and with designs not intended for future integration with
+ other services. After it subsumed the functions of network relaying and
+ Google searches, other desired features, which enhanced the IRC medium
+ and had not previously been considered due to the difficulty of
+ extending existing robots, were added to Pynfo, prompting the development
+ of an elementary plug-in system to further facilitate the integration
+ process.</p>
+
+ <h3>Architecture</h3>
+ <p>Pynfo performs such simple tasks as noting the last time an
+ individual spoke and querying the Google search engine, as well as
+ several more complex operations like relaying traffic between different
+ IRC networks and publishing channel logs through an HTTP interface.</p>
+
+ <p>Toward these ends, it is useful to abstract the functionality into
+ several different layers:</p>
+
+ <ul>
+ <li><p>The factory: All shared data, such as the channels a given user is
+ known to be in, the plugins currently loaded, and the addresses of servers
+ to connect to, is aggregated here. When it is necessary to make a
+ connection, the factory creates an instance of the appropriate Protocol
+ subclass, in a manner similar to this:
+
+ <pre class="python">
+ def buildProtocol(self, address):
+ for net in self.data['networks'].values():
+ if net.address == address:
+ break
+
+ proto = IRCProtocol(net)
+ self.allBots[net.alias] = proto
+ proto.factory = self
+ return proto
+ </pre>
+
+ The factory instance is created only once, and that instance persists
+ through the entire time a particular Pynfo bot operates.</p>
+ </li>
+
+ <li><p>The protocol: Each kind of service Pynfo can connect to has a
+ Protocol class associated with it, a class which handles the specifics
+ of communicating over this protocol. Unlike the factory, protocols
+ instances can be short lived and are created and destroyed as many times
+ as network connectivity demands. When a Pynfo robot shuts down and is
+ serialized to disk, all Protocol instances are destroyed and discarded,
+ to be created anew when the robot is restarted.</p>
+ </li>
+
+ <li><p>Plugins: These give Pynfo most of its functionality. From the
+ very simple logging module, which does no more than write strings to
+ disk, to the esoteric lookup module, which translates hostnames into
+ dotted-quads, to the informative dictionary module, which queries an <a
+ href="http://dict.org">online dictionary</a>, plugins come in all shapes
+ and sizes, and can be written to fill almost any niche.</p>
+ </li>
+ </ul>
+
+ <h3>Employing Components</h3>
+ <p>Twisted provides a <i>component</i> system which Pynfo relies on to
+ split up useful functionality used in different areas of the code. The
+ Interface class is the primary element in the component system, and is
+ used as a location for a semi-format definition of an API, as well as
+ documentation. Classes declare that they implement an Interface by
+ including it in their __implements__ tuple attribute. Interfaces can
+ also be added to classes by third parties using the registerAdapter()
+ function. This takes an Adapter type in addition to the interface being
+ registered and the type it is being registered for. Adapters are a
+ objects which can store additional state information and implement
+ functionality without being part of the classes that are "actually"
+ being operated upon. They, as their name suggests, adapt components to
+ conform to interfaces.</p>
+
+ <p>Components can implement interfaces themselves, or maintain a cache
+ of adapter objects for each interfaces that is requested of them. These
+ persist like any other attribute, and so state stored in adapters
+ remains associated with the component as long as that component exists, or
+ until the adapter is explicitly removed.</p>
+
+ <p>Pynfo's Factory class uses two adapters to implement two basic
+ Interfaces that many plugins find useful. The first is the IStorage
+ interface.
+
+ <pre class="python">
+ class IStorage(components.Interface):
+
+ def store(self, key, version, value):
+ """
+ Store any pickleable object
+ """
+
+ def retrieve(self, key, version):
+ """
+ Retrieve the previously stored object associated with key and
+ version
+ """
+ </pre>
+ An example usage of this interface is the PyPI plugin, which polls the
+ Python Package Index and reports updates to a configurable list of
+ outputs:
+
+ <pre class="python">
+ def init(factory):
+ global notifyChannels
+ store = factory.getComponent(interfaces.IStorage)
+ try:
+ notifyChannels = store.retrieve('pypi', __version__)
+ except error.RetrievalError:
+ notifyChannels = []
+
+ </pre>
+ <p>The module requests the component of factory which implements
+ IStorage, then attempts to load any previously stored version of
+ "notifyChannels". If none is found, it defaults to none. In the
+ finalizer below, this global is stored, using the same interfaced, to be
+ retrieved when the module is next initialized.</p>
+
+ <pre class="python">
+ def fini(factory):
+ s = factory.getComponent(interfaces.IStorage)
+ s.store('pypi', __version__, notifyChannels)
+ </pre>
+
+ The second interface allows low granularity scheduling of events:
+
+ <pre class="python">
+ class IScheduler(components.Interface):
+ MINUTELY = 60
+ HOURLY = 60 * 60
+ DAILY = 60 * 60 * 24
+ WEEKLY = 60 * 60 * 24 * 7
+
+
+ def schedule(self, period, fn, *args, **kw):
+ """
+ Cause a function to be invoked at regular intervals with the given
+ arguments.
+ """
+ </pre>
+ The Adapter which implements this interface is just as simple:
+ <pre class="python">
+ class SchedulerAdapter(components.Adapter):
+ __implements__ = (interfaces.IScheduler,)
+
+ def schedule(self, period, fn, *args, **kw):
+ from twisted.internet import reactor
+ def cycle():
+ fn(*args, **kw)
+ reactor.callLater(period, cycle)
+ reactor.callLater(period, cycle)
+ </pre>
+ </p>
+
+ <p>Implementing these interfaces as adapters using the component system
+ has two primary advantages over a simple inheritance or mixins approach.
+ First, it allows plugins to add completely new behavior to the system
+ without complex and fragile manipulation of the factory's __class__
+ attribute. This is a big win when it comes to plugins that want to
+ share new functionality with other plugins. For example, the "ignore"
+ plugin adds an IDiscriminating interface and an adapter which implements
+ it. Once this plugin is loaded, any other plugin can request the
+ component for IDiscriminating and add users to or remove users from the
+ ignore list.</p>
+
+ <h3>The Plugin Framework</h3>
+
+ <p>Before a module can be loaded and initialized as a plugin, it must be
+ located. This could be done with a simple use of
+ <code>os.listdir()</code>, or <code>__all__</code> could be set to include
+ each new plugin added. Twisted provides another way, though.</p>
+
+ <p>The <code>twisted.python.plugin</code> provides the most high-level
+ interface to the plugin system, a function called
+ <code>getPlugIns</code>. It usually takes one argument, a plugin type,
+ which is an arbitrary string used to categorize the different kinds of
+ plugins available on a system. Twisted's own "mktap" tool uses the
+ "tap" plugin type. For Pynfo, I have elected to use the "infobot"
+ string. <code>getPlugIns("infobot")</code> searches the system (by way
+ of PYTHONPATH) for files named "plugins.tml". These files contain
+ python source, and are run as such; a function, "register" is placed in
+ their namespace, and the most common action for them is to invoke this
+ function one or more times, providing information about a plugin. Here
+ is a snippet from one which Pynfo uses:</p>
+
+ <pre class="python">
+ register(
+ "Weather",
+ "Pynfo.plugins.weather",
+ description="Commands to check the weather at "
+ "various places around the world.",
+ type="infobot"
+ )
+ </pre>
+
+ <p>Any number of plugin.tml files may exist in the filesystem, allowing
+ per-user and even per-robot plugins to be installed, all without
+ modifying the Pynfo installation itself.
+
+ The second argument indicates the module which may be imported to get
+ this plugin. Pynfo traverses the resulting list, importing these modules,
+ and initializing them if necessary.</p>
+
+ </body>
+</html>
diff --git a/doc/historic/2003/pycon/applications/pynfo-chart.png b/doc/historic/2003/pycon/applications/pynfo-chart.png
new file mode 100644
index 0000000..7318b15
--- /dev/null
+++ b/doc/historic/2003/pycon/applications/pynfo-chart.png
Binary files differ
diff --git a/doc/historic/2003/pycon/conch/conch b/doc/historic/2003/pycon/conch/conch
new file mode 100755
index 0000000..00ed511
--- /dev/null
+++ b/doc/historic/2003/pycon/conch/conch
@@ -0,0 +1,98 @@
+#!/usr/bin/python
+from slides import Slide, Bullet, SubBullet, URL
+from twslides import Lecture
+
+lecture = Lecture(
+ "Twisted Conch: SSH in Python",
+ Slide("Introduction",
+ ),
+ Slide("Other implementations (servers)",
+ Bullet("OpenSSH",
+ SubBullet(URL("http://www.openssh.org")),
+ ),
+ Bullet("FSecure SSH",
+ SubBullet(URL("http://www.f-secure.com/products/ssh/")),
+ ),
+ Bullet("LSH",
+ SubBullet(URL("http://www.lysator.liu.se/~nisse/lsh/")),
+ ),
+ ),
+ Slide("Other implementations (clients)",
+ Bullet("PuTTY",
+ SubBullet(URL("http://www.chiark.greenend.org.uk/~sgtatham/putty/")),
+ ),
+ Bullet("TeraTerm",
+ SubBullet(URL("http://www.ayera.com/teraterm/")),
+ ),
+ Bullet("MindTerm",
+ SubBullet(URL("http://www.appgate.com/mindterm/")),
+ ),
+ ),
+ Slide("Why Twisted?",
+ Bullet("Asynchronous"),
+ Bullet("Python"),
+ Bullet("High-Level"),
+ ),
+ Slide("No Forking or Threads",
+ Bullet("Forking is expensive"),
+ Bullet("Threads are complicated/expensive, esp. in Python"),
+ Bullet("Asynch means no worrying about any of that"),
+ Bullet("Makes running a session 2x as fast in Conch as in OpenSSH"),
+ ),
+ Slide("Security - No Pointers",
+ SubBullet("No buffer overflows"),
+ SubBullet("No off-by-1 errors"),
+ SubBullet("No malloc/free bugs"),
+ SubBullet("No arbitrary code execution"),
+ ),
+ Slide("Security - High Level",
+ Bullet("Strong built-in library"),
+ Bullet("Exceptions"),
+ ),
+ Slide("Security - Not Root",
+ Bullet("Limits vulnerablity in a compromise"),
+ Bullet("Allows use of process limits/etc."),
+ ),
+ Slide("Interfacing with other software",
+ Bullet("OpenSSH interacts only through separate processes",
+ SubBullet("Expensive"),
+ SubBullet("Complicated"),
+ ),
+ Bullet("Conch can interact in-process",
+ SubBullet("Faster"),
+ SubBullet("Easy integration to other Twisted and Python libraries"),
+ ),
+ ),
+ Slide("Speed",
+ Bullet("C is faster than Python"),
+ Bullet("Interpreter cost is high for the client"),
+ Bullet("FSH-style connection caching helps a bit"),
+ Bullet("Psyco helps as well"),
+ ),
+ Slide("Age",
+ Bullet("Conch is new",
+ SubBullet("First commit was July 15, 2002"),
+ ),
+ Bullet("Hasn't had a security aduit"),
+ Bullet("Shouldn't be used in security-critical systems"),
+ ),
+ Slide("Applications with Conch",
+ Bullet("Reality: MUD framework"),
+ Bullet("Insults: async. replacement for curses in Conch apps"),
+ ),
+ Slide("Future Directions",
+ Bullet("Generic authentication forwarding"),
+ Bullet("Work on applications"),
+ Bullet("Auditing of the code"),
+ Bullet("Increase speed"),
+ Bullet("SFTP/SCP"),
+ Bullet("Key Agent"),
+ Bullet("DNSSEC"),
+ ),
+ Slide("Conclusion",
+ Bullet("Working implementation in Python"),
+ Bullet("Much room for improvement"),
+ ),
+)
+
+lecture.renderHTML(".", "conch-%d.html", css="main.css")
diff --git a/doc/historic/2003/pycon/conch/conch.html b/doc/historic/2003/pycon/conch/conch.html
new file mode 100644
index 0000000..0faab7e
--- /dev/null
+++ b/doc/historic/2003/pycon/conch/conch.html
@@ -0,0 +1,165 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<title>Twisted Conch: SSH in Python with Twisted</title>
+</head>
+
+<body>
+<h1>Twisted Conch: SSH in Python with Twisted</h1>
+
+<ul>
+<li>Paul Swartz
+ <a href="mailto:z3p@twistedmatrix.com">z3p@twistedmatrix.com</a></li>
+</ul>
+
+<h2>Introduction</h2>
+
+<p>Although it is a newcomer on the Secure Shell stage, Twisted Conch has quickly
+caught up with the two most popular free *nix implementations and the most
+popular free Windows implementation in terms of functionality. This rapid
+development time, as well as the stability and other advantages, owes much to
+Python and the Twisted networking framework.</p>
+
+<h2>Other implementations (servers)</h2>
+
+<p>Other than Conch, there are three popular server implementations. OpenSSH
+works with versions 1 and 2 of the protocol, and is the most popular on *nix
+systems. FSecure is more popular on Windows servers, and also works with both
+versions. LSH is newer, and implements version 2. All three are written in C,
+with LSH having some supporting Scheme code to generate C files.</p>
+
+<h2>Other implementations (clients)</h2>
+
+<p>On *nix, the SSH clients are provided by the server implementations (OpenSSH
+and LSH). On Windows, there are a couple of separate clients. PuTTY is the
+most popular and supports Telnet along with SSH1 and 2. TeraTerm recently
+incorporated SSH into the core: before it had been an extension module.
+MindTerm is the only implementation in this list to be written in a language
+other than C. It runs as a Java applet, allowing SSH to run on any computer
+with a JVM.</p>
+
+<h2>Why Twisted?</h2>
+
+<p>Why is Twisted ideal for this type of project? Firstly, it is an asynchronous
+library, meaning there are no worries about threading or concurrency issues.
+This means more developer time can be devoted to making the code work well,
+rather than just work. Second, Python lends itself to this kind of
+development: the code is easy to read and easy to write. Third, the Twisted
+library is high-level, so developers do not need to worry about select loops or
+callbacks. Twisted handles all of that and allows developers to concentrate on
+the code.</p>
+
+<h2>No forking/threads</h2>
+
+<p>Unlike OpenSSH, the Conch server does not fork a process for each incoming
+connection. Instead, it uses the Twisted reactor to multiplex the connections.
+The only fork done is to execute a process such as a shell, but running a shell
+is not necessary, in which case the entire protocol would be run in-process.
+One of the initial features of the server was an in-process Python interpreter
+which allowed a user to interact with the server as it was running. (It is
+currently disabled for security reasons.) Threads are only used to interface
+with synchronous libraries, such as PyPAM (Pluggable Authentication Modules
+support) or PyME (GPGME support). By not using forks or threads, the time it
+takes for the Conch server to start an SSH session is roughly half of the time
+it takes for OpenSSH. However, this does require that code in Conch be
+non-blocking, which is an obstacle for programmers not used to that style.</p>
+
+<h2>Security - No Pointers</h2>
+
+<p>OpenSSH, LSH, and PuTTY are all written in C. Many security holes are a result
+of problems with unsafe pointer usage, which is a large problem in C code.
+Many other security holes result from related issues, such as buffer overflows,
+off-by-one errors on arrays, and memory allocation/deallocation bugs. Python
+is pointer-safe, and so is not vulnerable to this class of hole. This also
+means that no arbitrary data from over-the-wire is ever run, meaning control
+always stays with the Conch server.</p>
+
+<h2>Security - High Level</h2>
+
+<p>Being written in Python provides more security than just pointer safety. The
+strong builtin library that comes with Python (including powerful data types
+like the list and dictionary) means that fewer wheels need to be reinvented.
+This limits the potential to make mistakes in implementation. Exceptions are
+another powerful tool. They centralize error handling, rather than the mix of
+methods that the C libraries use. All errors are caught and dealt with: this
+might mean that the server stops accepting connections, but it never
+compromises security.</p>
+
+<h2>Security - Not Root</h2>
+
+<p>Also, Conch does not need to run as root. In the default server, root
+privileges are used for two things: to bind to ports &lt; 1024, and to fork a
+process as a different user. If neither of these are needed, the server need
+not run as root at all. Even if they are, the server is only running as root
+for those small sections. The rest of the time, it runs under the effective
+user and group ID of the user who started the server. This limits the amount
+of damage that could be inflicted in the event of a compromise.</p>
+
+<h2>Interfacing with other software</h2>
+
+<p>OpenSSH can interact with subsystems such as SFTP only by executing a process
+to handle it. Not only is forking a process expensive, it limits the
+interaction to a generic bitstream, which leaves developers to determine how
+to interact with their users. Conch can run in the same process as other
+Python software, and is easily integrated with other Twisted servers. This
+allows for things like secure remote administration of a Twisted web server,
+encrypted communication to a Reality MUD, or secure remote object access using
+Perspective Broker. This saves the hassle and expense of forking, and allows
+Python developers to interact with Conch the way they know best: with Python.</p>
+
+<h2>Speed</h2>
+
+<p>No one can deny that compiled C is faster than Python. Some part of Conch use
+C (PyCrypto, TGMP) to speed frequent operations, but the majority of the code
+is in Python. The client suffers the most from this because of the time it
+takes to start the interpreter. Work is being done to speed up the client by
+caching connections. This does not eliminate the interpreter start-up cost,
+but it removes the cost of negotiating a new connection. This effort is
+similar to FSH (also in Python) but interacts more nicely with the SSH
+protocol. Psyco helps as well, offering a speedup of roughly 2x - 5x.</p>
+
+<h2>Age</h2>
+
+<p>As I said in the introduction, Conch is still a newcomer on the Secure Shell
+stage (The first commit for Conch was July 15, 2002.) Although Python solves
+a large class of holes, it is probable that other security holes are in the
+code. Until a full audit is conducted of Twisted and of Conch, it should not
+be used for security-critical systems.</p>
+
+<h2>Applications with Conch</h2>
+
+<p>One of the applications for Conch is with Reality, a MUD framework using
+Twisted. Conch makes it easy to allow secure connections to the MUD in
+addition or even in place of a standard Telnet connection. As problems
+such as character theft become more prevalent on the Internet, a secure
+interface becomes more important.</p>
+
+<p>More generally, work is being done on Insults, a replacement for libraries
+like Curses and S-Lang. It allows developers to write GUI code that
+interacts well with Conch and other Twisted software. Although it is in the
+initial stages of development, it shows much promise for the future.</p>
+
+<h2>Future Directions</h2>
+
+<p>There are several different directions for Conch to move in. One of the most
+interesting is system for generalized authentication forwarding. This would
+allow all authentication to be performed on a host that the user controls,
+which would help to stop vulnerabilities such as timing attacks. Second is
+more work with applications. Insults is becoming more powerful, and it will
+be interesting to see what it can be used for. Also important are auditing of
+the code and increasing the speed. These will make the code more useful in
+general, as well as improving security. Other ideas include direct support for
+SFTP/SCP, support for a key agent, and interfacing with Twisted Names to
+support DNSSEC.</p>
+
+<h2>Conclusion</h2>
+
+<p>Although it is new, Conch is a working implementation of the Secure Shell
+protocol. It is robust enough to serve as both the client and server on
+systems I and others use daily.</p>
+
+</body></html>
diff --git a/doc/historic/2003/pycon/conch/conchtalk.txt b/doc/historic/2003/pycon/conch/conchtalk.txt
new file mode 100755
index 0000000..d2552ee
--- /dev/null
+++ b/doc/historic/2003/pycon/conch/conchtalk.txt
@@ -0,0 +1,144 @@
+Introduction
+------------
+Although it is a newcomer on the Secure Shell stage, Twisted Conch has quickly
+caught up with the two most popular free *nix implementations and the most
+popular free Windows implementation in terms of functionality. This rapid
+development time, as well as the stability and other advantages, owes much to
+Python and the Twisted networking framework.
+
+Other implementations (servers)
+------------------------------
+Other than Conch, there are three popular server implementations. OpenSSH
+works with versions 1 and 2 of the protocol, and is the most popular on *nix
+systems. FSecure is more popular on Windows servers, and also works with both
+versions. LSH is newer, and implements version 2. All three are written in C,
+with LSH having some supporting Scheme code to generate C files.
+
+Other implementations (clients)
+-------------------------------
+On *nix, the SSH clients are provided by the server implementations (OpenSSH
+and LSH). On Windows, there are a couple of separate clients. PuTTY is the
+most popular and supports Telnet along with SSH1 and 2. TeraTerm recently
+incorporated SSH into the core: before it had been an extension module.
+MindTerm is the only implementation in this list to be written in a language
+other than C. It runs as a Java applet, allowing SSH to run on any computer
+with a JVM.
+
+Why Twisted?
+------------
+Why is Twisted ideal for this type of project? Firstly, it is an asynchronous
+library, meaning there are no worries about threading or concurrency issues.
+This means more developer time can be devoted to making the code work well,
+rather than just work. Second, Python lends itself to this kind of
+development: the code is easy to read and easy to write. Third, the Twisted
+library is high-level, so developers do not need to worry about select loops or
+callbacks. Twisted handles all of that and allows developers to concentrate on
+the code.
+
+No forking/threads
+------------------
+Unlike OpenSSH, the Conch server does not fork a process for each incoming
+connection. Instead, it uses the Twisted reactor to multiplex the connections.
+The only fork done is to execute a process such as a shell, but running a shell
+is not necessary, in which case the entire protocol would be run in-process.
+One of the initial features of the server was an in-process Python interpreter
+which allowed a user to interact with the server as it was running. (It is
+currently disabled for security reasons.) Threads are only used to interface
+with synchronous libraries, such as PyPAM (Pluggable Authentication Modules
+support) or PyME (GPGME support). By not using forks or threads, the time it
+takes for the Conch server to start an SSH session is roughly half of the time
+it takes for OpenSSH. However, this does require that code in Conch be
+non-blocking, which is an obstacle for programmers not used to that style.
+
+Security - No Pointers
+----------------------
+OpenSSH, LSH, and PuTTY are all written in C. Many security holes are a result
+of problems with unsafe pointer usage, which is a large problem in C code.
+Many other security holes result from related issues, such as buffer overflows,
+off-by-one errors on arrays, and memory allocation/deallocation bugs. Python
+is pointer-safe, and so is not vulnerable to this class of hole. This also
+means that no arbitrary data from over-the-wire is ever run, meaning control
+always stays with the Conch server.
+
+Security - High Level
+---------------------
+Being written in Python provides more security than just pointer safety. The
+strong builtin library that comes with Python (including powerful data types
+like the list and dictionary) means that fewer wheels need to be reinvented.
+This limits the potential to make mistakes in implementation. Exceptions are
+another powerful tool. They centralize error handling, rather than the mix of
+methods that the C libraries use. All errors are caught and dealt with: this
+might mean that the server stops accepting connections, but it never
+compromises security.
+
+Security - Not Root
+-------------------
+Also, Conch does not need to run as root. In the default server, root
+privileges are used for two things: to bind to ports < 1024, and to fork a
+process as a different user. If neither of these are needed, the server need
+not run as root at all. Even if they are, the server is only running as root
+for those small sections. The rest of the time, it runs under the effective
+user and group ID of the user who started the server. This limits the amount
+of damage that could be inflicted in the event of a compromise.
+
+Interfacing with other software
+--------------------------------------------
+OpenSSH can interact with subsystems such as SFTP only by executing a process
+to handle it. Not only is forking a process expensive, it limits the
+interaction to a generic bitstream, which leaves developers to determine how
+to interact with their users. Conch can run in the same process as other
+Python software, and is easily integrated with other Twisted servers. This
+allows for things like secure remote administration of a Twisted web server,
+encrypted communication to a Reality MUD, or secure remote object access using
+Perspective Broker. This saves the hassle and expense of forking, and allows
+Python developers to interact with Conch the way they know best: with Python.
+
+Speed
+---------------------
+No one can deny that compiled C is faster than Python. Some part of Conch use
+C (PyCrypto, TGMP) to speed frequent operations, but the majority of the code
+is in Python. The client suffers the most from this because of the time it
+takes to start the interpreter. Work is being done to speed up the client by
+caching connections. This does not eliminate the interpreter start-up cost,
+but it removes the cost of negotiating a new connection. This effort is
+similar to FSH (also in Python) but interacts more nicely with the SSH
+protocol. Psyco helps as well, offering a speedup of roughly 2x - 5x.
+
+Age
+---
+As I said in the introduction, Conch is still a newcomer on the Secure Shell
+stage (The first commit for Conch was July 15, 2002.) Although Python solves
+a large class of holes, it is probable that other security holes are in the
+code. Until a full audit is conducted of Twisted and of Conch, it should not
+be used for security-critical systems.
+
+Applications with Conch
+-----------------------
+One of the applications for Conch is with Reality, a MUD framework using
+Twisted. Conch makes it easy to allow secure connections to the MUD in
+addition or even in place of a standard Telnet connection. As problems
+such as character theft become more prevalent on the Internet, a secure
+interface becomes more important.
+More generally, work is being done on Insults, a replacement for libraries
+like Curses and S-Lang. It allows developers to write GUI code that
+interacts well with Conch and other Twisted software. Although it is in the
+initial stages of development, it shows much promise for the future.
+
+Future Directions
+-----------------
+There are several different directions for Conch to move in. One of the most
+interesting is system for generalized authentication forwarding. This would
+allow all authentication to be performed on a host that the user controls,
+which would help to stop vulnerabilities such as timing attacks. Second is
+more work with applications. Insults is becoming more powerful, and it will
+be interesting to see what it can be used for. Also important are auditing of
+the code and increasing the speed. These will make the code more useful in
+general, as well as improving security. Other ideas include direct support for
+SFTP/SCP, support for a key agent, and interfacing with Twisted Names to
+support DNSSEC.
+
+Conclusion
+----------
+Although it is new, Conch is a working implementation of the Secure Shell
+protocol. It is robust enough to serve as both the client and server on
+systems I and others use daily.
diff --git a/doc/historic/2003/pycon/conch/smalltwisted.png b/doc/historic/2003/pycon/conch/smalltwisted.png
new file mode 100644
index 0000000..4f7d04d
--- /dev/null
+++ b/doc/historic/2003/pycon/conch/smalltwisted.png
Binary files differ
diff --git a/doc/historic/2003/pycon/conch/twistedlogo.png b/doc/historic/2003/pycon/conch/twistedlogo.png
new file mode 100644
index 0000000..6226297
--- /dev/null
+++ b/doc/historic/2003/pycon/conch/twistedlogo.png
Binary files differ
diff --git a/doc/historic/2003/pycon/deferex/deferex-bad-adding.py b/doc/historic/2003/pycon/deferex/deferex-bad-adding.py
new file mode 100644
index 0000000..d124eaa
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-bad-adding.py
@@ -0,0 +1,8 @@
+def successCallback(result):
+ myResult = result + 1
+ print myResult
+ return myResult
+
+...
+
+adder.callRemote("add", 1, 1).addCallback(successCallback)
diff --git a/doc/historic/2003/pycon/deferex/deferex-chaining.py b/doc/historic/2003/pycon/deferex/deferex-chaining.py
new file mode 100644
index 0000000..cee9bea
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-chaining.py
@@ -0,0 +1,13 @@
+from twisted.internet import reactor, defer
+
+A = defer.Deferred()
+def X(result):
+ B = defer.Deferred()
+ reactor.callLater(2, B.callback, result)
+ return B
+def Y(result):
+ print result
+A.addCallback(X)
+A.addCallback(Y)
+A.callback("hello world")
+reactor.run()
diff --git a/doc/historic/2003/pycon/deferex/deferex-complex-failure.py b/doc/historic/2003/pycon/deferex/deferex-complex-failure.py
new file mode 100644
index 0000000..bcc38e2
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-complex-failure.py
@@ -0,0 +1,30 @@
+from deferexex import adder
+
+class MyExc(Exception):
+ "A sample exception"
+
+class MyObj:
+
+ def blowUp(self, result):
+ self.x = result
+ raise MyExc("I can't go on!")
+
+ def trapIt(self, failure):
+ failure.trap(MyExc)
+ print 'error (', failure.getErrorMessage(), '). x was:', self.x
+ return self.x
+
+ def onSuccess(self, result):
+ print result + 3
+
+ def whenTrapped(eslf, result):
+ print 'Finally, result was', result
+
+ def run(self, o):
+ o.callRemote("add", 1, 2).addCallback(
+ self.blowUp).addCallback(
+ self.onSuccess).addErrback(
+ self.trapIt).addCallback(
+ self.whenTrapped)
+
+MyObj().run(adder)
diff --git a/doc/historic/2003/pycon/deferex/deferex-complex-raise.py b/doc/historic/2003/pycon/deferex/deferex-complex-raise.py
new file mode 100644
index 0000000..8005e45
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-complex-raise.py
@@ -0,0 +1,12 @@
+class MyExc(Exception):
+ "A sample exception."
+
+try:
+ x = 1 + 3
+ raise MyExc("I can't go on!")
+ x = x + 1
+ print x
+except MyExc, me:
+ print 'error (',me,'). x was:', x
+except:
+ print 'fatal error! abort!'
diff --git a/doc/historic/2003/pycon/deferex/deferex-forwarding.py b/doc/historic/2003/pycon/deferex/deferex-forwarding.py
new file mode 100644
index 0000000..c2fa6f9
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-forwarding.py
@@ -0,0 +1,9 @@
+from twisted.spread import pb
+
+class LocalForwarder(flavors.Referenceable):
+ def remote_foo(self):
+ return str(self.local.baz())
+
+class RemoteForwarder(flavors.Referenceable):
+ def remote_foo(self):
+ return self.remote.callRemote("baz").addCallback(str)
diff --git a/doc/historic/2003/pycon/deferex/deferex-listing0.py b/doc/historic/2003/pycon/deferex/deferex-listing0.py
new file mode 100644
index 0000000..e0de3ce
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-listing0.py
@@ -0,0 +1,18 @@
+
+class DocumentProcessor:
+ def __init__(self):
+ self.loadDocuments(self.callback, mySrv, "hello")
+
+ def loadDocuments(callback, server, keyword):
+ "Retrieve a set of documents!"
+ ...
+
+ def callback(self, documents):
+ try:
+ for document in documents:
+ process(document)
+ finally:
+ self.cleanup()
+
+ def cleanup(self):
+ ...
diff --git a/doc/historic/2003/pycon/deferex/deferex-listing1.py b/doc/historic/2003/pycon/deferex/deferex-listing1.py
new file mode 100644
index 0000000..399eb2b
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-listing1.py
@@ -0,0 +1,6 @@
+def prettyRequest(server, requestName):
+ return server.makeRequest(requestName
+ ).addCallback(
+ lambda result: ', '.join(result.asList())
+ ).addErrback(
+ lambda failure: failure.printTraceback())
diff --git a/doc/historic/2003/pycon/deferex/deferex-listing2.py b/doc/historic/2003/pycon/deferex/deferex-listing2.py
new file mode 100644
index 0000000..d124eaa
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-listing2.py
@@ -0,0 +1,8 @@
+def successCallback(result):
+ myResult = result + 1
+ print myResult
+ return myResult
+
+...
+
+adder.callRemote("add", 1, 1).addCallback(successCallback)
diff --git a/doc/historic/2003/pycon/deferex/deferex-simple-failure.py b/doc/historic/2003/pycon/deferex/deferex-simple-failure.py
new file mode 100644
index 0000000..34f6b29
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-simple-failure.py
@@ -0,0 +1,9 @@
+from deferexex import adder
+
+def blowUp(result):
+ raise Exception("I can't go on!")
+
+def onSuccess(result):
+ print result + 3
+
+adder.callRemote("add", 1, 2).addCallback(blowUp).addCallback(onSuccess)
diff --git a/doc/historic/2003/pycon/deferex/deferex-simple-raise.py b/doc/historic/2003/pycon/deferex/deferex-simple-raise.py
new file mode 100644
index 0000000..cd89b1a
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-simple-raise.py
@@ -0,0 +1,3 @@
+x = 1 + 3
+raise Exception("I can't go on!")
+print x
diff --git a/doc/historic/2003/pycon/deferex/deferex.html b/doc/historic/2003/pycon/deferex/deferex.html
new file mode 100644
index 0000000..a4fb171
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex.html
@@ -0,0 +1,499 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+<title>Generalization of Deferred Execution in Python</title>
+</head>
+
+<body>
+<h1>Generalization of Deferred Execution in Python</h1>
+
+<p>Glyph Lefkowitz</p>
+
+<div>
+<div>Twisted Matrix Labs</div>
+<div><a href="mailto:glyph@twistedmatrix.com">glyph@twistedmatrix.com</a></div>
+</div>
+
+<h2>Overview</h2>
+
+<p>A deceptively simple architectural challenge faced by many multi-tasking
+applications is gracefully doing nothing. Systems that must wait for the
+results of a long-running process, network message, or database query while
+continuing to perform other tasks must establish conventions for the semantics
+of waiting. The simplest of these is blocking in a thread, but it has
+significant scalability problems. In asynchronous frameworks, the most common
+approach is for long-running methods to accept a callback that will be executed
+when the command completes. These callbacks will have different signatures
+depending on the nature of the data being requested, and often, a great deal of
+code is necessary to glue one portion of an asynchronous networking system to
+another. Matters become even more complicated when a developer wants to wait
+for two different events to complete, requiring the developer to &quot;juggle&quot;
+the callbacks and create a third, mutually incompatible callback type to handle
+the final result. </p>
+
+<p>This paper describes the mechanism used by the Twisted framework for waiting
+for the results of long-running operations. This mechanism, the <code>Deferred</code>,
+handles the often-neglected problems of error handling, callback juggling,
+inter-system communication and code readability. </p>
+
+<p> In a framework like Twisted, the ability to glue two existing components
+together with a minimum of mediating code is paramount. Considering that the
+vast majority of the code in Twisted is asynchronous I/O handling, it is
+imperative that the mechanism for relaying the data between the output from one
+system into the input of another be competitive with the simplicity of passing
+the return value of one method to the argument of another. It was also
+important to use only no new syntax to avoid confusing programmers who already
+have experience with Python, and establish no dependencies on anything which
+would break compatibility with existing Python code or C / Java
+extensions. </p>
+
+<h2>Other Popular Approaches</h2>
+
+<p>There are several traditional approaches to handling concurrency that have
+been taken by application frameworks in the past. Each has its own
+drawbacks.</p>
+
+<h3>Threads</h3>
+
+<p>The problems with using threads for concurrency in systems that need to
+scale is fairly well-documented. However, many systems that use asynchronous
+multiplexing for I/O and system-level tasks, but run application code in a
+thread. Zope's threaded request handling is a good example of this model.</p>
+
+<p>It is optimal, however, to avoid <em>requiring</em> threads for any part of
+a framework. Threading has a significant cost, especially in Python. The
+global interpreter lock destroys any performance benefit that threading may
+yield on SMP systems, and introduces significant complexity into both framework
+and application code that needs to be thread-safe.</p>
+
+<p>A full discussion of the pros and cons of threads is beyond the scope of
+this paper, however, using threads merely for blocking operations is clearly
+overkill. Since each thread represents some allocation of resources, all of
+those resources are literally sitting idle if they are doing nothing but
+waiting for the results from a blocking call.</p>
+
+<p>In a fairly traditional networking situation, where the server is
+asynchronously multiplexed, this waste of resources may be acceptable for
+special-purpose, simple client programs, since only a few will be run at a
+time. To create a generic system, however, one must anticipate cases when the
+system in question is not only a multi-user server or a single-user client, but
+also a multi-user hybrid client/server.</p>
+
+<p>A good example of this is a high-volume web spider. A spider may have a
+server for administrative purposes, but must also be able to spawn many
+requests at once and wait for them all to return without allocating undue
+resources for each request. The non-trivial overhead of threads, in addition
+to sockets, would be a very serious performance problem. </p>
+
+<h3>Callback Conventions</h3>
+
+<p>At some level, any system for handling asynchronous results in Python will
+be based on callback functions. The typical way to present this to the
+application programmer is to have all asynchronous methods accept a callback as
+one of their arguments.</p>
+
+<p>This approach is usually standardized by giving the callback having a
+standard name (&quot;callback&quot;) or a particular position (first argument, last
+argument). Even systems which rigorously adhere to such standardization run
+into problems, however.</p>
+
+<p>This approach does work for a variety of events. It is unwieldy when one is
+attempting to write asynchronous &quot;conversations&quot; that involve multiple
+stages. The first problem that we notice is the lack of error-handling. If an
+error occurs in normal Python code, Exception handling provides clean and
+powerful semantics for handling it. </p>
+
+<a href="deferex-listing0.py" class="py-listing">Document Processor Example</a>
+
+<p>In an asynchronous method such as the one given above, traditional
+exceptions fall short. What if an error occurs retrieving the documents from
+storage? Do we call the callback with an error rather than a result?</p>
+
+<h3>Language Modifications</h3>
+
+<p>Other languages handle this by associating different semantics with
+threading, or providing different constructs altogether for concurrency. This
+has the disadvantage that these languages aren't Python. Even Stackless Python
+is problematic because it lacks integration with the wide variety of libraries
+that Python provides access to. </p>
+
+<p>The design of <code>Deferred</code> draws upon some of these other languages, and this
+section will cover several languages and their impact.</p>
+
+<p>In particular, the following list of languages were influential:</p>
+
+<ul>
+ <li>Erlang</li>
+ <li>Mozart/Oz</li>
+ <li>E</li>
+ <li>Scheme</li>
+ <li>Smalltalk</li>
+</ul>
+
+<p> E, Smalltalk, and Scheme proved particularly influential. In E's, there is
+a sharp distinction between objects which are synchronously accessible and
+those which are asynchronously accessible. The original use for
+<code>Deferred</code>s was to represent results from Perspective Broker method
+calls. E was interesting in that the entire execution environment had
+assumptions about networking built in. E's &quot;eventually&quot; operator <a
+href="#steigler">[stiegler]</a> is what originally inspired the distinction
+between &quot;a method which returns X&quot; and &quot;a method which returns a
+<code>Deferred</code> that fires X&quot;. </p>
+
+<p>
+Smalltalk was influential in that its syntax for closures provided some
+precedent for thinking about the continuation of a &quot;conversation&quot; of execution
+as itself an object with methods. The original thought-experiment that lead to
+<code>Deferred</code>s was an attempt to write some Squeak code that looked like this:
+
+<pre>
+(object callRemote: &quot;fooBar&quot;) andThen: [ result |
+ Transcript show: result.
+ ] orElse: [ failure |
+ failure printTraceback.
+ ]
+</pre>
+
+The hypothetical <code>callRemote</code> method here would return an object
+with the method <code>andThen:orElse:</code> that took 2 code blocks, one for
+handling results and the other for handling errors.
+</p>
+
+<p>It was challenging to write enough Smalltalk code to make anything
+interesting happen with this paradigm, but within the existing framework of
+Twisted, it was easy to convert several request/response idioms to use this
+sort of object. Now that Twisted has dropped python 1.5.2 compatibility, and
+2.1 is the baseline version, we can use <code>nested_scopes</code> <a
+href="#hylton">[hylton]</a> and anonymous functions to make the code look
+similar to this original model. </p>
+
+<p> Scheme, of course, provides <code>call-with-current-continuation</code> (or
+<code>call/cc</code>), the mind-bending control structure which has been a
+subject of much debate in language-design circles. <code>call/cc</code> may
+have provided more a model of things to avoid than a real inspiration, though.
+While it is incredibly powerful, it creates almost as many problems as it
+solves. In particular, the interaction between continuations and
+<code>try:finally:</code> is undefined <a href="#pitman">[pitman]</a>, since it
+is impossible to determine the final time the protected code will be run. The
+strongest lesson from <code>call/cc</code> was to only take as much state in
+the <code>Deferred</code> as necessary, and to avoid playing tricks with implicit context.
+</p>
+
+<p>The mechanisms that these languages use, however, often rely upon deeper
+abstractions that make their interpreters less amenable than Python's to
+convenient, idiomatic integration with C and UNIX. Scheme's
+<code>call/cc</code> requires a large amount of work and creativity to
+integrate with &quot;C&quot; language libraries, as C. Tismer's work in
+Stackless Python Python has shown. <a href="#tismer">[tismer]</a> </p>
+
+<h2>Basics of Deferreds</h2>
+
+<p>After several months of working with Twisted's callback-based
+request/response mechanisms, it became apparent that something more was
+necessary. Often, errors would silently cause a particular process to halt.
+The syntax for a multi-stage asynchronous process looked confusing, because
+multiple different interfaces were being invoked, each of which taking multiple
+callbacks. The complexity of constructing these stages was constantly being
+exposed to the application developer, when it shouldn't really concern them.
+</p>
+
+<p>In order to make gluing different request/response systems together easy, we
+needed to create a more uniform way of having them communicate than a simple
+convention. In keeping with that goal, we reduced several conventions into one
+class, <code>Deferred</code>, so that the request system could return a
+<code>Deferred</code> as output and the responder could accept a <code>Deferred</code> as input..
+<code>Deferred</code>s are objects which represent the result of a request that is not yet
+available. It is suggested that any methods which must perform long-running
+calculations or communication with a remote host return a <code>Deferred</code>.</p>
+
+<p>This is similar to the Promise pattern, or lazy evaluation, except that it
+is a promise that will not be resolved synchronously. The terminology usually
+used to describe a <code>Deferred</code> is &quot;a <code>Deferred</code> that will fire&quot; a particular
+result.</p>
+
+<p><code>Deferred</code>s have a small interface, which boils down to these five methods,
+plus convenience methods that call them:
+
+<ul>
+ <li><code>addCallbacks(self, callback, errback=None, callbackArgs=None,
+ callbackKeywords=None, errbackArgs=None, errbackKeywords=None)</code></li>
+ <li><code>callback(result)</code></li>
+ <li><code>errback(result)</code></li>
+ <li><code>pause()</code></li>
+ <li><code>unpause()</code></li>
+</ul>
+
+</p>
+
+<p>In general, code that initially returns <code>Deferred</code>s will be framework code,
+such as a web request or a remote method call. This means that code that uses
+the framework will call <code>addCallbacks</code> on the <code>Deferred</code> that is
+returned by the framework. When the result is ready, the callback will be
+triggered and the client code can process the result. Usually the utility
+methods <code>addCallback</code> and <code>addErrback</code> are used.
+</p>
+
+<p>Using <code>addCallbacks</code> has slightly different semantics than using
+<code>addCallback</code> followed by <code>addErrback</code>;
+<code>addCallbacks</code> places the callback and the errback &quot;in
+parallel&quot;, meaning if there is an error in your callback, your errback will
+not be called. Thus using <code>addCallbacks</code> has either/or semantics;
+either the callback or the errback will be called, but not both.</p>
+
+<a href="deferex-listing1.py" class="py-listing">Fictitious Request Example</a>
+
+<p>The example given shows a method which returns a <code>Deferred</code> that will fire a
+formatted string of the result of a given request. The return value of each
+callback is passed to the first argument of the next.</p>
+
+<h2>Generalized Error Handling</h2>
+
+<p>As described above in the section on using callbacks for asynchronous result
+processing, one of the most common application-level problems in an
+asynchronous framework is an error that causes a certain task to stop
+executing. For example, if an exception is raised while hashing a user's
+password, the entire log-in sequence might be halted, leaving the connection in
+an inconsistent state.</p>
+
+<p>One way that Twisted remedies this is to have reasonable default behavior in
+circumstances such as this: if an uncaught exception is thrown while in the
+<code>dataReceived</code> callback for a particular connection, the connection
+is terminated. However, for multi-step asynchronous conversations, this is not
+always adequate.</p>
+
+<p>Python's basic exception handling provides a good example for an
+error-handling mechanisms. If the programmer fails to account for an error, an
+uncaught exception stops the program and produces information to help track it
+down. Well-written python code never has to manually detect whether an error
+has occurred or not: code which depends on the previous steps being successful
+will not be run if they are not. It is easy to provide information about an
+error by using attributes of exception objects. It is also easy to relay
+contextual information between successful execution and error handlers, because
+they execute in the same scope.</p>
+
+<p><code>Deferred</code> attempts to mimic these properties as much as possible in an
+asynchronous context.</p>
+
+<h3>Reasonable Defaults</h3>
+
+<p>When something unexpected goes wrong, the program should emit some debugging
+information and terminate the asynchronous chain of processing as gracefully as
+possible.</p>
+
+<p>Python exceptions do this very gracefully, with no effort required on the
+part of the developer at all.</p>
+
+<a href="deferex-simple-raise.py" class="py-listing">Simple Catastrophic Exception</a>
+
+<p><code>Deferred</code>s provide a symmetrical facility, where the developer may register a
+callback but then forego any error processing.</p>
+
+<a href="deferex-simple-failure.py" class="py-listing">Simple Catastrophic Deferred Failure</a>
+
+<p>In this example, the onSuccess callback will never be run, because the
+blowUp callback creates an error condition which is not handled. </p>
+
+<h3>No Ambiguity about Failure</h3>
+
+<p>It is impossible to provide a reasonable default behavior if failure is
+ambiguous. Code should never have to manually distinguish between success and
+failure. An error-processing callback has a distinct signature to a
+result-processing callback.</p>
+
+<p>Forcing client code to manually introspect on return values creates a common
+kind of error; when the success of a given long-running operation is assumed,
+it appears to work, and it is easier (and less code) to write a callback that
+only functions properly in a successful case, and creates bizarre errors in a
+failure case. A simple example:.</p>
+
+<a class="py-listing" href="deferex-bad-adding.py">Common Error Pattern</a>
+
+<p>In this example, when the remote call to add the two numbers succeeds,
+everything looks fine. However, when it fails, <code>result</code> will be an
+exception and not an integer: therefore the printed traceback will say
+something unhelpful, like:</p>
+
+<pre>TypeError: unsupported operand types for +: 'instance' and 'int'</pre>
+
+<h3>Rich Information about Errors</h3>
+
+<p>It should be easy for developers to distinguish between fatal and non-fatal
+errors. With Python exceptions, you can do this by specifying exception
+classes, which is a fairly powerful technique.</p>
+
+<a href="deferex-complex-raise.py" class="py-listing">Complex Python Exception</a>
+
+<p>With <code>Deferred</code>, we planned to have a syntactically simple technique for
+accomplishing something similar. The resulting code structure is tends to be a
+bit more expansive than the synchronous equivalent, due to the necessity of
+giving explicit names to the functions involved, but it can be just as easy to
+follow.</p>
+
+<a href="deferex-complex-failure.py" class="py-listing">Complex Deferred Failure</a>
+
+<p>In this example, we have a callback chain that begins with the result of a
+remote method call of 3. We then encounter a <code>MyExc</code> error raised
+in <code>blowUp</code>, which is caught by the errback <code>trapIt</code>.
+The 'trap' method will re-raise the current failure unless its class matches
+the given argument, so the error will continue to propagate if it doesn't
+match, much like an <code>except:</code> clause.</p>
+
+<h3>Easy Propagation of Context</h3>
+
+<p>While it is dangerous to implicitly propagate too much context (leading to
+problems similar to those with threads), we wanted to make sure that it is easy
+to move context from one callback to the next, and to convert information in
+errors into successful results after the errors have been handled. </p>
+
+<p>Both <code>addCallback</code> and <code>addErrback</code> have the signature
+<code>callable, *args, **kw</code>. The additional arguments are passed
+through to the registered callback when it is invoked. This allows us to
+easily send information about the current call to the error-handler in the same
+way as the success callback.</p>
+
+<h2>Patterns of Usage</h2>
+
+<p>Since <code>Deferred</code> is designed for a fairly specific class of problems, most
+places it is used tend to employ certain idioms.</p>
+
+<h3>Request-ID Dictionary</h3>
+
+<p>If you are implementing a symmetric, message-oriented protocol, you will
+typically need to juggle an arbitrary number of outstanding requests at once.
+The normal pattern for doing this is to create a dictionary mapping a request
+number to a <code>Deferred</code>, and firing a <code>Deferred</code> when a response with a given
+request-ID associated with it arrives.</p>
+
+<p>A good example of this pattern is the Perspective Broker protocol. Each
+method call has a request, but it is acceptable for the peer to make calls
+before answering requests. Few protocols are as extensively permissive about
+execution order as PB, but any full-fledged RPC or RMI protocol will enable
+similar interactions. The MSN protocol implementation in Twisted also uses
+something similar.</p>
+
+<h3> Sometimes Synchronous Interface </h3>
+
+<p>When writing interfaces that application programmers will be implementing
+frequently, it is often convenient to allow them to either return either a
+<code>Deferred</code> or a synchronous result. A good example of this is Twisted's Woven, a
+dynamic web content system.
+</p>
+
+<p> The processing of any XML node within a page may be deferred until some
+results are ready, be they results of a database query, a remote method call,
+an authentication request, or a file upload. Many methods that may return
+Nodes may also return <code>Deferred</code>s, so that in either case the application
+developer need return the appropriate value. No wrapping is required if it is
+synchronous, and no manual management of the result is required if it is not.
+</p>
+
+<p>This is the best way to assure that an application developer will never need
+to care whether a certain method's results are synchronous or not. The first
+usage of this was in Perspective Broker, to allow easy transparent forwarding
+of method calls. If a <code>Deferred</code> is returned from a remotely accessible method,
+the result will not be sent to the caller until the <code>Deferred</code> fires. </p>
+
+<a href="deferex-forwarding.py" class="py-listing">Forwarding Local and Remote
+Interfaces</a>
+
+<h3><code>callRemote</code></h3>
+
+<p>Ideally, all interactions between communicating systems would be modeled as
+asynchronous method calls. Twisted Words, the Twisted chat server, treats any
+asynchronous operation as a subset of the functionality of Perspective Broker,
+using the same interface. Eventually, the hope is to make greater use of this
+pattern, and abstract asynchronous conversations up another level, by having
+the actual mechanism of message transport wrapped so that client code is only
+aware of what asynchronous interface is being invoked.</p>
+
+<h2>Advanced Features</h2>
+
+<p>The first &quot;advanced&quot; feature of <code>Deferred</code>s is actually
+used quite frequently. As discussed previously, each <code>Deferred</code> has
+not one, but a chain of callbacks, each of which is passed the result from the
+previous callback. However, the mechanism that invokes each callback is itself
+an implementor of the previously-discussed &quot;Sometimes Synchronous
+Interface&quot; pattern - a callback may return either a value or a
+<code>Deferred</code>.</p>
+
+<p>For example, if we have a <code>Deferred</code> A, which has 2 callbacks: X,
+which returns a deferred B, that fires the result to X in 2 seconds, and Y,
+which prints its result, we will see the string &quot;hello&quot; on the screen
+in 2 seconds. While it may sound complex, this style of coding one
+<code>Deferred</code> which depends on another looks very natural.</p>
+
+<a class="py-listing" href="deferex-chaining.py">Chaining 2
+<code>Deferred</code>s Together</a>
+
+<p>In this way, any asynchronous conversation may pause to wait for an
+additional request, without knowing in advance of running the first request
+what all the requests will be.</p>
+
+<p>The other advanced feature of <code>Deferred</code>s is not terribly common,
+but is still useful on occasion. We have glossed over the issue of
+&quot;pre-executed&quot;<code>Deferred</code>s so far, e.g. <code>Deferred</code>s
+which have already been called with a callback value before client code adds
+callbacks to them. The default behavior, which works in almost every
+situation, is simply to call the callback immediately (synchronously) as it is
+added. However, there are rare circumstances where race conditions can occur
+when this naive approach is taken.</p>
+
+<p>For this reason, <code>Deferred</code> provides <code>pause</code> and
+<code>unpause</code> methods, allowing you to put a <code>Deferred</code> into
+a state where it will stop calling its callbacks as they are added; this will
+allow you to set up a series of communicating <code>Deferred</code>s without
+having anything execute, complete your setup work, and then unpause the
+process.</p>
+
+<p>In this way, you can create centralized choke-points for caring about whether
+a process is synchronous or not, and completely ignore this problem in your
+application code. For example, in the now-obsolete Twisted Web Widgets system
+(a dynamic web content framework that predates woven), it was necessary to make
+sure that certain <code>Deferred</code>s were always called in order, so the page would
+render from top to bottom. However, the user's code did not need to concern
+itself with this, because any <code>Deferred</code>s for which synchronous callback
+execution would have been an issue were passed to user code paused.</p>
+
+<h2>Conclusion</h2>
+
+<p><code>Deferred</code>s are a powerful abstraction for dealing with
+asynchronous results. Having a uniform approach to asynchronous conversations
+allows Twisted APIs to provide a level of familiarity and flexibility for
+network programmers that approaches that of domain-specific languages, but
+still provides access to all of Python's power.</p>
+
+<h2>Acknowledgements</h2>
+
+<p>I would like to thank the entire Twisted team, for making me realize what a
+good idea I had hit upon with <code>Deferred</code>s.</p>
+
+<p>Special thanks go to Andrew Bennetts and Moshe Zadka, for implementing the
+portion of Twisted used to generate this, and other, papers, and to Ying Li and
+Donovan Preston for last-minute editorial assistance..</p>
+
+<h2>References</h2>
+
+<ol>
+
+ <li><a name="stiegler"></a>Marc Stiegler, <a
+ href="http://www.skyhunter.com/marcs/ewalnut.html#SEC19">The E Language in a
+ Walnut</a>, <i>erights.org</i></li>
+
+ <li><a name="hylton"></a>Jeremy Hylton, <a
+href="http://www.python.org/peps/pep-0227.html" >PEP 227, &quot;Statically
+Nested Scopes&quot;</a></li>
+
+ <li><a name="pitman"></a>Kent Pitman, <a
+href="http://www.nhplace.com/kent/PFAQ/unwind-protect-vs-continuations.html"
+ >UNWIND-PROTECT vs. Continuations</a>, <i>Kent Pitman's Personal FAQ</i></li>
+
+ <li><a name="tismer">Christian Tismer, <a
+ href="http://www.stackless.com/spcpaper.htm">Continuations and Stackless
+ Python</a>, <i>Proceedings of the Sixth International Python Conference</i>
+ </a>
+ </li>
+
+</ol>
+
+</body>
+</html>
diff --git a/doc/historic/2003/pycon/deferex/deferexex.py b/doc/historic/2003/pycon/deferex/deferexex.py
new file mode 100644
index 0000000..71c116e
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferexex.py
@@ -0,0 +1,16 @@
+
+# DEFERred EXecution EXamples
+
+### make sure errors come out in order
+import sys
+from twisted.python import log
+log.logerr = sys.stdout
+
+# Create a pseudo "remote" object for executing this stuff
+from twisted.spread.util import LocalAsRemote
+class Adder(LocalAsRemote):
+ def async_add(self, a, b):
+ print 'adding', a, b
+ return a + b
+
+adder = Adder()
diff --git a/doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning b/doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning
new file mode 100644
index 0000000..d41d3af
--- /dev/null
+++ b/doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning
@@ -0,0 +1,97 @@
+#!/usr/bin/python
+
+from slides import *
+from twslides import *
+
+class PythonSource:
+ def __init__(self, content):
+ self.content = content
+ def toHTML(self):
+ return '<pre class="python">%s</pre>' % (self.content,)
+
+lecture = Lecture(
+ "Changing the Type of Literals",
+ Slide("New-style classes",
+ Bullet("In 2.2+, built-in types can be subclassed"),
+ Bullet("These can be created explicitly by using their name", SubBullet(
+ Bullet("For example, an int subclass that displays itself in roman numerals"),
+ Bullet("print RomanNumeral(13) -> XIII"),
+ )),
+ ),
+ Slide("Literals are less accessable",
+ Bullet("When you write [] or 7, the list or int type is instantiated"),
+ Bullet("This behavior seems inaccessable"),
+ Bullet("While this makes for more readable code, it limits the scope of possible evil"),
+ ),
+ Slide("Throw in an extension module...",
+ Bullet("intrinsics.so exposes one function, 'replace'"),
+ Bullet("It takes two arguments", SubBullet(
+ Bullet("A type object to replace"),
+ Bullet("The type object with which to replace it"),
+ )),
+ Bullet("Magic is performed, and the new type is now used whenever the old one would have been"),
+ ),
+ Slide("An example",
+ PythonSource("""\
+class RomanNumeral(int):
+ def __str__(self):
+ # Regular code for formatting roman numerals
+
+old_int = intrinsics.replace(int, RomanNumeral)
+print 13
+"""
+ ),
+ Bullet("The output is simply the roman numerals XIII"),
+ ),
+ Slide("intrinsics.c - The replacement",
+ PRE("""\
+PyObject*
+intrinsics_replace(PyObject* self, PyObject* args) {
+ static PyTypeObject* const types[] = {
+ &PyInt_Type, &PyLong_Type, &PyFloat_Type, &PyComplex_Type,
+ &PyBool_Type, &PyBaseObject_Type, &PyDict_Type, &PyTuple_Type,
+ &PyBuffer_Type, &PyClassMethod_Type, &PyEnum_Type, &PyProperty_Type,
+ &PyList_Type, &PyStaticMethod_Type, &PySlice_Type, &PySuper_Type,
+ &PyType_Type, &PyRange_Type, &PyFile_Type, &PyUnicode_Type,
+ &PyString_Type,
+ NULL
+ };
+
+ int i = 0;
+ PyObject *old, *new;
+ PyTypeObject* space;
+
+ if (!PyArg_ParseTuple(args, "OO:replace", &old, &new))
+ return NULL;
+"""
+ ),
+ ),
+ Slide("intrinsics.c - The actual replacement",
+ PRE("""\
+ while (types[i]) {
+ if (types[i] == (PyTypeObject*)old) {
+ space = PyObject_New(PyTypeObject, &PyType_Type);
+ *space = *(types[i]);
+ *(types[i]) = *(PyTypeObject*)new;
+ break;
+ }
+ ++i;
+ }
+ if (!types[i]) {
+ PyErr_SetString(replace_error, "unknown type");
+ return NULL;
+ }
+ Py_INCREF(new);
+ Py_INCREF(space);
+ return (PyObject*)space;
+}
+"""
+ ),
+ ),
+ Slide("This is the wrong answer",
+ Bullet("The right answer is to add more flexibility to the Python compiler"),
+ Bullet("This is a lot less code, though"),
+ )
+)
+
+lecture.renderHTML(".", "intrinsics-lightning-%d.html", css="stylesheet.css")
diff --git a/doc/historic/2003/pycon/lore/lore-presentation b/doc/historic/2003/pycon/lore/lore-presentation
new file mode 100755
index 0000000..59a4d6a
--- /dev/null
+++ b/doc/historic/2003/pycon/lore/lore-presentation
@@ -0,0 +1,108 @@
+#!/usr/bin/python2.2
+# Moshe -- current content is estimated at about 15 minutes
+from slides import NumSlide, Slide, Bullet, SubBullet, PRE, URL
+from twslides import Lecture
+
+
+lecture = Lecture(
+ "Lore: A Document Generation System",
+ Slide("Introduction",
+ Bullet("Document generation system"),
+ Bullet("Input a strict subset of XHTML"),
+ Bullet("Output -- nicely formatted HTML and LaTeX"),
+ Bullet("Used to generate >200 pages of Twisted documentation"),
+ ),
+ Slide("History",
+ Bullet("Twisted needed documentation -- and a format"),
+ Bullet("Reluctance to add dependence on a big system"),
+ Bullet("Wanted something quick and easy -- subset of HTML!"),
+ Bullet("Needs matured: table of contents, printed version"),
+ Bullet("Enter Lore"),
+ ),
+ Slide("Goals",
+ Bullet("Easy to use for authors"),
+ Bullet("Easy to install"),
+ Bullet("(Uncommon) Source format should be readable"),
+ ),
+ Slide("Contents",
+ Bullet("twisted.lore Python package"),
+ Bullet("'lore' command-line program"),
+ Bullet("Comes with every Twisted installation"),
+ Bullet("In particular -- works on Linux, Win32, Mac"),
+ Bullet("In particular -- supports Python 2.1, 2.2, 2.3 alpha"),
+ ),
+ Slide("Alternatives - HTML",
+ Bullet("Too flexible"),
+ Bullet("No support for needed idioms", SubBullet(
+ Bullet("Special-purpose Python markup"),
+ Bullet("Tables of contents"),
+ Bullet("Inlining")),
+ ),
+ Bullet("Renders badly to dead trees with current tools"),
+ ),
+ Slide("Alternatives - LaTeX",
+ Bullet("Very good at printed results"),
+ Bullet("Model makes alternative parsers near-impossible"),
+ Bullet("Renderers to HTML are buggy and fragile"),
+ ),
+ Slide("Alternatives - Docbook",
+ Bullet("Using correctly requires too much work", SubBullet(
+ Bullet("Write a DTD with special elements"),
+ Bullet("Write Jade stylesheets"))),
+ Bullet("Lore is probably smaller than docbook specialization"),
+ ),
+ Slide("Alternatives - Texinfo",
+ Bullet("Next slide, please"),
+ ),
+ Slide("Lore goodies",
+ Bullet("Special tag to mark classes/modules/functions", SubBullet(
+ Bullet("Can be made to point to auto-generated docs")),
+ ),
+ Bullet("Inline code-examples", SubBullet(
+ Bullet("No need to escape all those <, > and &")),
+ ),
+ Bullet("Syntax-highlight Python code"),
+ ),
+ Slide("hlint - A lint-like program",
+ Bullet("Checks for many common errors"),
+ Bullet("Unhandled elements"),
+ Bullet("Misspelled (or miscased) class names"),
+ Bullet("Checks Python code for syntax errors"),
+ ),
+ Slide("Extending Lore",
+ Bullet("Easily done with some Python code"),
+ Bullet("Input-enhancements decide which output formats to handle"),
+ Bullet("Example: math-lore, Lore with LaTeX formulae"),
+ ),
+ Slide("HTML Output",
+ Bullet("HTML is a flexible output format"),
+ Bullet("Documents often have to integrate with a site"),
+ Bullet("Lore produces HTML documents based on a template"),
+ Bullet("Lore uses only HTML 'class' attributes, never 'font'",
+ SubBullet(Bullet("Plays nice with CSS")),
+ ),
+ ),
+ Slide("Man Pages",
+ Bullet("Lore has a program to convert man pages to Lore documents"),
+ Bullet("Man pages are written anyway"),
+ Bullet("No man output: the format is too limited"),
+ ),
+ Slide("Small Example",
+ PRE("""\
+<html>
+<head>
+<title>Example</title>
+</head>
+<body>
+<h1>Example</h1>
+<p>Simple paragraph<span class="footnote">footnote</span></p>
+</body>
+</html>
+""")),
+ Slide("Future Directions",
+ Bullet("More output formats"),
+ Bullet("Some more classes - abstract, bibliography"),
+ ),
+)
+
+lecture.renderHTML(".", "lore-%d.html", css="main.css")
diff --git a/doc/historic/2003/pycon/lore/lore-slides.html b/doc/historic/2003/pycon/lore/lore-slides.html
new file mode 100755
index 0000000..19171f8
--- /dev/null
+++ b/doc/historic/2003/pycon/lore/lore-slides.html
@@ -0,0 +1,187 @@
+<html>
+ <head><title>Lore: A Document Generation System</title></head>
+
+ <body>
+ <h1>Lore: A Document Generation System</h1>
+
+ <div class="author">Andrew Bennetts &lt;andrew@puzzling.org&gt;</div>
+ <div class="author">(Twisted Lore maintainer)</div>
+
+ <h2>Introduction</h2>
+ <ul>
+ <li>Document generation system</li>
+ <li>Input format is essentially a subset of XHTML</li>
+ <li>Outputs nicely formatted HTML and LaTeX</li>
+ <li>Used to generate &gt;200 pages of Twisted documentation</li>
+ </ul>
+
+ <h2>History</h2>
+ <ul>
+ <li>Twisted needed documentation -- and a format</li>
+ <li>We didn't want to depend on a big system
+ <ul>
+ <li>The lower the barrier for documentation contributions, the
+ better</li>
+ </ul>
+ </li>
+ <li>We wanted something quick and easy
+ <ul>
+ <li>Lots of people already know simple HTML</li>
+ <li>People were already using HTML to write docs</li>
+ </ul>
+ </li>
+ <li>Our needs matured: table of contents, printable version</li>
+ <li>So we created Lore</li>
+ </ul>
+
+ <h2>Goals</h2>
+ <ul>
+ <li>Easy to use for authors</li>
+ <li>Easy to install</li>
+ <li>(Uncommon) Source format should be readable
+ <ul>
+ <li>Even to non-hackers</li>
+ </ul>
+ </li>
+ </ul>
+
+ <h2>Small Example</h2>
+ <pre>
+&lt;html&gt;
+&lt;head&gt;
+&lt;title&gt;Example&lt;/title&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;h1&gt;Example&lt;/h1&gt;
+&lt;p&gt;Simple paragraph&lt;span class="footnote"&gt;footnote&lt;/span&gt;&lt;/p&gt;
+&lt;/body&gt;
+&lt;/html&gt;
+</pre>
+
+ <h2>Contents</h2>
+ <ul>
+ <li>twisted.lore Python package</li>
+ <li><code class="shell">lore</code> command-line program</li>
+ <li>Comes with every Twisted installation</li>
+ <li>In particular -- works on Linux, Win32, Mac</li>
+ <li>In particular -- supports Python 2.1, 2.2, 2.3 alpha</li>
+ </ul>
+
+ <h2>Alternatives - HTML</h2>
+ <ul>
+ <li>Too flexible</li>
+ <li>No support for needed idioms
+ <ul>
+ <li>Special-purpose Python markup</li>
+ <li>Tables of contents</li>
+ <li>Inlining</li>
+ </ul>
+ </li>
+ <li>Renders badly to dead trees with current tools</li>
+ </ul>
+
+ <h2>Alternatives - LaTeX</h2>
+ <ul>
+ <li>Very good at printed results</li>
+ <li>LaTeX's design makes alternative parsers near-impossible</li>
+ <li>Renderers to HTML are buggy and fragile
+ <ul>
+ <li>Although the Python Standard Library seems to cope :-)</li>
+ </ul>
+ </li>
+ </ul>
+
+ <h2>Alternatives - Docbook</h2>
+ <ul>
+ <li>Using correctly requires too much work
+ <ul>
+ <li>Write a DTD with special elements</li>
+ <li>Write Jade stylesheets</li>
+ </ul>
+ </li>
+ <li>Lore is probably smaller than docbook specialization</li>
+ </ul>
+
+ <h2>Alternatives - Texinfo</h2>
+ <ul>
+ <li>Next slide, please</li>
+ </ul>
+
+ <h2>Lore goodies</h2>
+ <ul>
+ <li>Special tag to mark classes/modules/functions
+ <ul>
+ <li>Can be made to point to auto-generated docs</li>
+ </ul>
+ </li>
+
+ <li>Inline code-examples
+ <ul>
+ <li>No need to escape all those &lt;, &gt; and &amp;</li>
+ </ul>
+ </li>
+
+ <li>Syntax-highlight Python code</li>
+ </ul>
+
+ <h2>'lore -o lint': A lint-like tool</h2>
+ <ul>
+ <li>Checks for many common errors
+ <ul>
+ <li>Invalid XML</li>
+ <li>Unhandled elements</li>
+ <li>Misspelled (or miscased) class names</li>
+ <li>Checks Python code for syntax errors</li>
+ </ul>
+ </li>
+ </ul>
+
+ <h2>Extending Lore</h2>
+ <ul>
+ <li>Easily done with some Python code</li>
+ <li>Input-enhancements decide which output formats to handle</li>
+ <li>Example: math-lore, Lore with LaTeX formulae</li>
+ </ul>
+
+ <h2>Extending Lore (cont'd)</h2>
+ <div class="pause" />
+ <ul>
+ <li>Another example: These slides!</li>
+ <li>The <code>lore-slides</code> plugin can output to
+ <ul>
+ <li>Magicpoint</li>
+ <li>HTML (one page per slide)</li>
+ <li>HTML (one big page)</li>
+ </ul>
+ </li>
+ </ul>
+
+ <h2>HTML Output</h2>
+ <ul>
+ <li>HTML is a flexible output format</li>
+ <li>Documents often have to integrate with a site</li>
+ <li>Lore produces HTML documents based on a template</li>
+ <li>Lore uses only HTML <code>class</code> attributes, never <code>font</code>
+ <ul>
+ <li>Plays nice with CSS</li>
+ </ul>
+ </li>
+ </ul>
+
+ <h2>Man Pages</h2>
+ <ul>
+ <li>Lore has a program to convert man pages to Lore documents</li>
+ <li>Man pages are written anyway</li>
+ <li>No man output: the format is too limited</li>
+ </ul>
+
+ <h2>Future Directions</h2>
+ <ul>
+ <li>More output formats</li>
+ <li>Some more classes -- abstract, bibliography</li>
+ <li>Index</li>
+ </ul>
+
+ </body>
+</html>
+
diff --git a/doc/historic/2003/pycon/lore/lore.html b/doc/historic/2003/pycon/lore/lore.html
new file mode 100644
index 0000000..da71590
--- /dev/null
+++ b/doc/historic/2003/pycon/lore/lore.html
@@ -0,0 +1,791 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<title>The Lore Document Generation Framework</title>
+</head>
+
+<body>
+
+<h1>The Lore Document Generation Framework</h1>
+
+<ul>
+<li>Moshe Zadka
+ <a href="mailto:moshez@twistedmatrix.com">moshez@twistedmatrix.com</a></li>
+<li>Andrew Bennetts
+ <a href="mailto:spiv@twistedmatrix.com">spiv@twistedmatrix.com</a></li>
+</ul>
+
+<h2>Abstract</h2>
+
+<p>Lore is a documentation generation system which uses a limited subset
+of XHTML, together with some class attributes, as its source format. This
+allows for lower barrier of entry than many other similar systems, since HTML
+authoring tools are plentiful
+as is knowledge of HTML writing. As an added advantage, the source format
+is viewable directly, so that even if Lore is not available the documentation
+is useful. It currently outputs LaTeX and HTML, which allows for most
+use-cases.</p>
+
+<p>Lore is currently in use by the Twisted project to generate its
+documentation for versions 1.0.1 and above.</p>
+
+<h2>History</h2>
+
+<p>At the beginning of Twisted's life cycle, as with any self-respecting
+free software project, it came completely devoid of documentation. As
+Twisted progressed in maturity, the Twisted development team realized
+that documentation is necessary.</p>
+
+<p>Since at that time the Twisted development
+team did not want the overhead of integrating
+a full-scale document generation framework into its build infrastructure,
+documents were written for the least common denominator -- plain HTML.
+When the Twisted team wanted the documentation to be
+featured on the web site, it was desirable to have them integrated with
+the web site's look and feel. Thus, <code class="shell">generate-domdocs</code>
+was born as a simple XML-based command line hack which improved the look of the
+documents so they would share the look and feel of the other pages in the web
+site, including a standard header and footer. As
+<code class="shell">generate-domdocs</code>
+slowly grew more and more features, it gradually became too large to maintain.
+The authors, members of the Twisted development team, decided that in order to
+make it more maintainable, it should be refactored into a
+library and by the way also add alternate output formats. Some of the documents
+which were reluctant to be transformed into alternate formats were fixed,
+and guidelines for making compatible documents were drafted. Those documents,
+together with the conversion code, are the Lore documentation generation
+system.</p>
+
+<h2>Introduction</h2>
+
+<p>Lore is documentation generation system which is a part of the
+<a href="http://twistedmatrix.com">Twisted</a> framework. It uses
+the Twisted XML parsing framework
+(<code class="API" base="twisted.web">microdom</code>) to parse compliant XHTML
+and generate the various output formats from it.</p>
+
+<p>Lore consists of a Python package, <code class="API">twisted.lore</code>,
+and a command-line program: <code class="shell">lore</code>, which
+generates HTML output (which is more presentation-oriented than the source
+format), LaTeX or runs an linter, depending on command-line arguments.</p>
+
+<p>In the case where the default output of Lore is not exactly suited to a
+Lore user,
+it is possible to subclass the output generators and customize their behavior.
+This could be done for many purposes, from straight-forward additions like
+adding a new <code>span</code> or <code>div</code> class to advanced tweaking
+such as changing the way Lore does image conversion on LaTeX output.</p>
+
+<p>Lore uses reflection intensively to make adding new features as simple
+as adding a new method, without the need for awkward registration schemes.
+Thus, adding another check to the linter or letting
+Lore handle the <code>link</code> element in some way require only the addition
+of one method.</p>
+
+<h2>Goals</h2>
+
+<p>Lore was written when the Twisted team felt it needed to write documentation
+and looked for a documentation format. Looking through alternatives, the
+best one seemed to be the Python way, using LaTeX format and
+<code class="shell">latex2html</code>. However, the Python way has its share
+of problems, not the least of which is <code class="shell">latex2html</code>
+being a long and crufty Perl program whose Perl APIs, which are the
+only way to add support for custom markup, change every version.</p>
+
+<p>Since documentation writing is important, a documentation system with
+minimal impact on the writer would be desirable. While LaTeX certainly has
+very little impact in terms of markup overhead, it has a very big impact
+both in terms of installed base (installing LaTeX on UNIX systems or
+Windows is non-trivial at best) and in terms of familiarity.</p>
+
+<p>HTML has the benefit of being directly readable on every post-1995
+computer, so the installed base is as big as could be hoped for. It also has
+the benefit of being easily parsed, at least in its new XHTML guise.</p>
+
+<p>The goals of Lore were taken to be:</p>
+
+<ul>
+<li>Source files directly readable.</li>
+<li>At least output to modern (CSS-based) HTML.</li>
+<li>Easily parsed by third-parties.</li>
+</ul>
+
+<h2>Source Format</h2>
+
+<h3>Description</h3>
+
+<p>Lore's source format is a subset of XHTML; all Lore source documents are
+valid XHTML documents. The XHTML tags that Lore allows are:
+<code>html</code>, <code>title</code>, <code>head</code>, <code>body</code>,
+<code>h1</code>, <code>h2</code>, <code>h3</code>, <code>ol</code>,
+<code>ul</code>, <code>dl</code>, <code>li</code>, <code>dt</code>,
+<code>dd</code>, <code>p</code>, <code>code</code>, <code>img</code>,
+<code>blockquote</code>, <code>a</code>, <code>cite</code>, <code>div</code>,
+<code>span</code>, <code>strong</code>, <code>em</code>, <code>pre</code>,
+<code>q</code>, <code>table</code>, <code>tr</code>, <code>td</code>,
+<code>th</code> and <code>style</code>.
+</p>
+
+<p>We would like to stress the omission of the <code>font</code> tag (which is
+deprecated in HTML 4.01 anyway). Instead of using <code>font</code>, Lore
+mandates the use of stylesheets
+and the <code>class</code> attribute, and in particular Lore defines several
+classes, such as <code>footnote</code>, <code>API</code>,
+<code>py-listing</code>. The use of classes on <code>div</code> and
+<code>span</code> elements effectively allows XHTML to be arbitrarily
+extensible without needing to define custom tags.</p>
+
+<p>Further discouraging explicit style decision, Lore deprecates the
+<code>style</code> attribute which allowing HTML (and XHTML) authors to embed
+pieces of the stylesheet in the document. Though Lore properly processes
+such documents, they are against the specification of Lore -- and
+the Lore lint-like problem finder will complain.</p>
+
+<h3>Advantages and Disadvantages</h3>
+
+<p>Requiring XHTML rather than just HTML greatly simplifies the code to
+manipulate Lore source, because we can use standard XML libraries. For
+documentation authors, the difference is negligible -- and any mistakes made in
+balancing tags can be easily found using the linter.
+Since tag balancing problems, in many cases, cause a discrepancy between
+author intention and the result, it is better to balance the tags anyway.</p>
+
+<p>Like LaTeX, Lore encourages authors to focus on content, letting the
+presentation take care of itself. This is an inherently restrictive approach,
+but results in much more consistent and higher-quality output.</p>
+
+<p>The Lore source format is quite usable (if somewhat plain) as an end-format.
+Any web browser can read it, and it does not require special stylesheet support,
+JavaScript or any other modern HTML additions. It is also, as intended,
+straightforward to create and edit documents in this format.</p>
+
+<p>However, reading the source format directly has some major limitations,
+which are inherent in the combination of the facilities which render HTML
+and the requirement that the format will be easily writable, and easy to
+modify, using any standard text editor.
+The limitations include:</p>
+
+<ul>
+<li>There is no table of contents.</li>
+<li>Footnotes interrupt the flow of text (although stylesheet tricks can
+alleviate this to an extent).</li>
+<li>Python source is not syntax highlighted.</li>
+<li>File inclusions are implemented as hyper-links.</li>
+</ul>
+
+<h2>Output Formats</h2>
+
+<p>The two most important formats, for the end-user, are the computer screen and
+pages of print outs. Any other format should be first and foremost be thought
+of as a prelude to these final formats.</p>
+
+<p>The easiest computer-screen oriented format is HTML. However, the HTML
+which is most comfortable and useful to the end-user is not necessarily
+easy to write and modify.
+For example, it is painful to manually write a table of contents, and even more
+painful to keep it updated as sections are added, removed or changed. However,
+when reading a long document having a table of contents, with hyperlinks
+into the sections, is a boon.
+Thus, even though both Lore's source and one output format are HTML, an
+HTML to HTML conversion is still necessary, paradoxical though it may sound.</p>
+
+<p>For printable output, the most widely supported formats are PostScript
+and Portable Document Format. On UNIX systems PostScript is often preferred,
+since there are many tools for manipulating it and printing it (and PostScript
+printers are more common in the UNIX world). On Windows and Apple computers,
+Portable Document Format (PDF) is preferred because of the ease of installation
+of the necessary tools. Mac OS X, though being technically a UNIX, supports
+PDF natively.</p>
+
+<p>Directly generating PostScript or PDF, however, is hard. Since these formats
+are very low-level, the application generating them must do the hard work
+of calculating line breaks, guessing hyphenation points and deciding on fonts.
+Since these tasks are already implemented by LaTeX, Lore just generates LaTeX
+code and lets the user run LaTeX to generate PostScript and
+<code class="shell">ps2pdf</code> to generate PDF. Granted, this still causes
+the problems with the difficulties of installing LaTeX. It is
+possible to implement direct Lore to PDF converter, though this hasn't been
+done yet, by using <code>pdflib</code>.</p>
+
+<h3>HTML</h3>
+
+<p>The HTML to HTML converter works by running a series of transformations on
+the Document Object Model (DOM) tree of the parsed document, and then
+writing it out. The most important transformation is that of throwing
+away anything outside the <code>body</code> element, and putting the
+<code>body</code> element inside a template file. This allows large
+parts of the common layout code to be customized without modifying or writing
+any Python code.</p>
+
+<p>Each step is implemented as a separate function, to allow Lore-using
+Python programmers to customize which tree transformations to do in their
+own code, without forcing them to rewrite functionality in Lore. In addition,
+other output generators might perform a subset of these transformations
+on the input tree before processing it -- and indeed, this is being used
+even in Lore itself.</p>
+
+<p>One of the steps taken is caused by a need which is common in large
+Python frameworks: many of the class or module names are deeply nested,
+but are commonly referred to by just their last one or two components
+in writing. However, the user would like to know the full name of the
+class or module name, and where to look up the API documentation -- but
+without having the complete name thrust upon him during the flow of text
+each time the module is mentioned.</p>
+
+<p>Lore makes sure that each class or module name which is mentioned will
+appear at least once using its full name, and afterwards use a common
+short name, regardless of how the author wrote it up. This frees authors
+from needing to observe, manually, this useful rule in their documents.</p>
+
+<p>The HTML Lore outputs aims to be the poster boy of graceful degradation.
+Thus, for example, while footnotes always appear as hyper-links to the footnote
+text, browsers which respect the <code>title</code> attribute (which is usually
+rendered as a tooltip) will also show the beginning of the footnote while
+hovering above the hyper-link.</p>
+
+<p>Lore avoids using the <q>font</q> or <q>color</q> tags and attributes,
+preferring to use HTML classes and using a stylesheet to specify graphical
+design decisions. This allows the Lore user to customize the presentation of
+the output without touching Python code. Since most often the stylesheet
+link is found in the <code>head</code> element, this is determined by
+the by the template.</p>
+
+<p>Lore uses the same approach even for syntax-highlighting Python code,
+generating such elements as
+<code>&lt;span class="keyword"&gt;if&lt;/span&gt;</code>.</p>
+
+<h3>LaTeX</h3>
+
+<p>The LaTeX home page describes LaTeX as a <q>high-quality typesetting system,
+with features designed for the production of technical and scientific
+documentation.</q> LaTeX is very popular for generating printable content,
+building on Donald Knuth's TeX system to generate nearly optimal output
+by putting together much of the typesetting industry's experience in the
+form of a program and adding sophisticated algorithms for line-breaking and
+hyphenation.</p>
+
+<p>It is very common for document generation systems to avoid generating
+printable output themselves, instead letting LaTeX do the hard work, and
+Lore is no exception.</p>
+
+<p>Lore can output LaTeX in two modes: article mode, in which it generates
+a complete article ready to be be processed, and a section mode in which
+it generates a LaTeX file whose top-level element is a section. Such a file
+is usually included in some other LaTeX file via the include mechanism.
+Twisted itself uses mainly the section mode, and includes everything in the
+file <code class="shell">book.tex</code>, which is later processed to generate
+the Twisted book.</p>
+
+<p>While, conceivably, other modes could be done (a chapter mode or a subsection
+mode) there has not been any demand for those. In the case of demand, supplying
+these would be very few lines of Python code (less than 10), which can even
+be done by subclassing existing classes and avoiding the modification of Lore
+itself.</p>
+
+<h3>Docbook</h3>
+
+<p>Docbook output is currently experimental. Its chief use to Lore would
+be in generating Texinfo, which is the source for the GNU info documentation
+format.</p>
+
+<h2>Lint</h2>
+
+<p>Very early in the Lore development life-cycle it was found that a good
+Lint-like tool is necessary to find errors without necessitating a full
+compilation to all formats and sometimes even browsing the results. Because
+Lore was written to accommodate a large set of already existing documents
+(which were not previously checked for potential problems), such a tool
+was very useful so that finding a problem in one document would not mean
+this problem needs to be manually searched, and corrected, in all the other
+documents.</p>
+
+<p>Lore's linter tries to find problems in documents
+that would either stop the conversion to other formats by Lore completely
+(for example, by being not well-formed XML), or that would make it less useful
+(for example, by warning about tags or classes that are not supported by
+Lore).</p>
+
+<p>The linter even detects more exotic problems,
+including:</p>
+<ul>
+ <li><code>pre</code> elements containing lines over 80 characters. Long lines
+ can be ugly to render in some output formats, and even impossible to
+ render in others.</li>
+ <li>Explicit use of the <code>"</code> character in a non-pre or non-code
+ environment. This makes a big difference for high-quality typographical
+ output targets like LaTeX, which
+ have distinct left- and right-quote characters.</li>
+ <li>Python code that isn't syntactically valid, with a bit of magic to account
+ for this idiom:
+<pre class="python">
+for x in sequence:
+ ...
+</pre>
+ This check caught a surprisingly large number of errors in the Twisted
+ documentation!</li>
+ <li><code>h1</code> contents being equal to <code>title</code> contents.
+ HTML is somewhat unique in that it has two places to specify the logical
+ idea of <q>title</q>. Since other output formats do not support that,
+ in Lore papers, the contents of both must be the same.</li>
+</ul>
+
+<p>Since many of the incremental improvements done to Lore found a problem
+in the existing documentation files, the linter has been
+an important part of the Lore development effort. One may even argue that
+part of the reason other documentation generation systems produce suboptimal
+output for their <q>non-native</q> application is the lack of a linting
+tool.</p>
+
+<p>Finally, if the linter gives a false positive, that is
+it emits a warning for something that isn't a problem in a particular situation,
+the user can add an <code>hlint="off"</code> attribute to the offending tag, and
+the linter will ignore it. This is necessary only very rarely.</p>
+
+<p>The chief design decision made in the linter, after
+painful experience when running <code class="shell">tidy</code>, is that
+<em>it must never change the document</em>. Thus, while the linter
+will be as pedantic as possible finding
+errors, it never changes the contents. This is particularly important
+when dealing with version control systems, where spurious changes can
+render <code class="shell">diff</code> listings useless.</p>
+
+<h2>Features</h2>
+
+<h3>Python Syntax Highlighting</h3>
+
+<p>All existing syntax highlighters for Python used pre-<code>tokenize</code>
+techniques to analyse the Python code. As a result, they were cumbersome
+and non-standard. The Lore developers decided that writing a Python
+HTML syntax-highlighter would be easier than modifying one of the existing
+ones. A syntax-highlighter was built on top of a null-tokenizer: that is,
+a tokenizer which emits the <em>exact same</em> characters as the input.
+This allowed easy debugging of the parsing code.</p>
+
+<p>The only non-trivial code in the syntax highlighter is when dealing
+with whitespace which is not significant syntactically, since the tokenizer
+does not report it. However, since the tokenizer does report row and column,
+when the code sees a discrepancy between where the previous token ended
+and the current token starts, it adds whitespace to make up for
+the discrepancy.</p>
+
+<p>When writing out the HTML, the only difference between that and the
+null-tokenizer is the wrapping of each token by a <code>span</code>
+tag with the appropriate class and escaping.</p>
+
+<p>Note that the basic Python tokenizer does not distinguish between the
+various roles of the production <q>NAME</q> (that is, a string of alphanumeric
+and
+underscore characters starting with an underscore or a letter) in Python.
+The tokenizer Lore uses adds that information by having a simple state machine:
+if the word is a keyword, there is nothing to be determined; otherwise, it
+depends on the last detected name -- <code>class</code> or
+<code>def</code> mean it is a function or class names, and after a
+<code>class</code>/<code>def</code> and until a <code>:</code>, everything
+is a <q>parameter</q> or a superclass.</p>
+
+<p>The Python syntax highlighter Lore uses can be found in the
+<code class="API">twisted.python.htmlizer</code>.</p>
+
+<h3>File Inclusion</h3>
+
+<p>Often, when writing detailed documents, the author wishes to test his
+examples or even use examples from a working project. Pasting such examples
+directly into the HTML has both the usual problems of pasting code -- the
+version in the document will not benefit from bug fixes or enhancement to
+the original version -- and the problem that the HTML needs proper escaping,
+which is a tedious and error-prone procedure if done manually.
+Both problems are solved by Lore's <q>listing</q> mechanism. The
+<q>listing</q> mechanism converts HTML such as</p>
+
+<pre>
+&lt;a href="foo.py" class="py-listing&gt;foo.py&lt;/a&gt;
+</pre>
+
+<p>into inclusion of the <code class="shell">foo.py</code> file. It will always
+be properly escaped for whatever output format. It will
+also be syntax-highlighted, just as if it had been included verbatim.</p>
+
+<p>A similar class, <code>html-listing</code> is available for inclusion
+of HTML files.</p>
+
+<h3>API Reference Links</h3>
+
+<p>Twisted's documentation frequently references API documentation. In Lore,
+the name of an API such as
+<code class="API">twisted.internet.defer.Deferred</code> is marked up as</p>
+
+<pre>
+&lt;code class="API" base="twisted.internet.defer"&gt;Deferred&lt;/code&gt;
+</pre>
+
+<p>This will unambiguously link to
+<code class="API">twisted.internet.defer.Deferred</code>, even though it is
+displayed as
+<q><code class="API" base="twisted.internet.defer">Deferred</code></q>. Lore
+produces API links that work with
+<a href="http://epydoc.sourceforge.net">epydoc</a>,
+but could easily be adapted for another API documentation generator; in fact,
+Lore originally worked with happydoc.
+In addition, in the HTML output, Lore will add a <code>title</code>
+attribute to the API reference, containing the full name of the link.</p>
+
+<h3>Cross references</h3>
+
+<p>A collection of documents will typically refer to each other, for instance to
+avoid re-explaining some central concept. In HTML, cross-referencing
+is implemented as linking:</p>
+
+<pre>
+See &lt;a href="defer.html"&gt;Deferring Execution&lt;/a&gt;.
+</pre>
+
+<p>As a collection of HTML documents, this works with no changes. Other output
+formats do linking in other ways. When Lore is used to convert a collection of
+source HTML files into a single LaTeX book, each file is its own section, and
+the links are automatically converted into cross-references. Thus the example
+above might be rendered as <q>See Deferring Execution (page 163).</q></p>
+
+<p>Lore also recognizes <em>fragment identifiers</em> in links, so that a link
+to <code>glossary.html#psu</code> will be cross-referenced to that part of the
+glossary named <q>psu</q>, not just the whole glossary. This ensures that the
+page the reader is referred to is the correct one.</p>
+
+<h2>Man Support</h2>
+
+<p>Man pages are a fact of life on UNIX, and every self-respecting command
+line program is expected to come with one. The man format, implemented as
+troff macros, is somewhat arcane. Since, when Lore was written, we already
+had written man pages, the decision was to convert them to HTML rather than
+try to rewrite them in HTML and design a man output format.</p>
+
+<p>A limited parser for man pages is available in the
+<code class="API">twisted.lore.man2lore</code> module. It is not yet
+exposed via any public command line program.</p>
+
+<p>Earlier attempts, using <code class="shell">groff -Thtml</code> to
+generate HTML and then post-process it into Lore-compatible HTML
+were crufty and unmaintainable. It seems the man format shares some
+of LaTeX's problem: being written as a macro package over a powerful
+processor, it is too flexible for its own good. Fortunately, the subset
+normally used in man pages is quite small, so heuristically parsing man pages is
+much easier than the same task with LaTeX.</p>
+
+<h2>Comparisons</h2>
+
+<h3>HTML</h3>
+
+<p>HTML, when invented by Tim Berners-Lee, was meant to be a simple language
+for writing and sharing documents. With the explosion of the web, HTML has
+grown to a confusing jumble of logical and presentation features, with more
+layers, such as CSS, dumped on top of it. As a result, a modern browser is
+a complicated beast. That given, it is perhaps understandable that today's
+browsers do a sub-standard job at printing. Thus, while being extremely
+well suited to the world wide web, HTML is significantly lacking, at least
+in today's application market, when it comes to paper output. It might
+be possible to write an application to properly convert HTML with CSS to
+PostScript or PDF -- however, it would probably be much more complicated
+than Lore. Moreover, the portability of such an application would
+be worse of the portability of Lore itself, which currently only depends
+on Python 2.1 or higher and the Twisted framework.</p>
+
+<p>Limiting HTML to a small subset of features enables Lore to be small
+and readable while remaining useful. By including the <code>class</code>
+attribute among those features, Lore is also extensible.</p>
+
+<h3>LaTeX</h3>
+
+<p>When it comes to paper output, LaTeX cannot be out done except by a skilled
+typesetter designing and implementing. However, the architecture of LaTeX
+presents
+significant problems when trying to view LaTeX online. LaTeX is written
+as a macro layer above TeX rather than a preprocessor. Thus, all of TeX's
+power is available, and sometimes used, in LaTeX. TeX is non-trivial to
+parse and format by anyone short of Donald Knuth -- it contains such commands
+as to change the tokenizer by modifying which characters are considered
+word characters or even which character is the command character.
+In fact, the authors are not aware of any application which handles the
+full power of TeX without being based on the original TeX code.</p>
+
+<p>All this makes LaTeX extremely difficult to parse, and even partial attempts
+to parse LaTeX are big and cumbersome -- for example,
+<code class="shell">latex2html</code>. It is thus difficult to convert
+LaTeX to something appropriate to online viewing.</p>
+
+<h3>LyX</h3>
+
+<p>LyX's internal source format is not well documented, and the only supported
+way to write it is using the LyX GUI. Thus it is inherently limiting to
+documentation authors. In addition, it is not trivial to write LyX preprocessors
+to save documentation authors tedious work.</p>
+
+<h3>Docbook</h3>
+
+<p>Docbook is a big standard, with non-trivial to install tool-set. Writing
+Docbook is different than most other document generation formats, so it
+takes significant training to write. In addition, using Docbook for
+a specific project usually requires writing custom DSSSL stylesheets
+in a scheme-like language, and additional XML DTD snippets. Writing
+these was quite possibly comparable to writing Lore, and Lore has the advantage
+of being written in Python.</p>
+
+<h3>Texinfo</h3>
+
+<p>Texinfo imposes a significant effort on authors. Many things need to
+be written twice, and the error messages leave a lot to be desired.
+After starting to work on the Lore texinfo output format the authors
+are grateful they have never had to write Texinfo by hand.</p>
+
+<h2>Techniques</h2>
+
+<h3>Visitor Pattern</h3>
+
+<p>When generating LaTeX, Lore does it via a visitor pattern while visiting
+the nodes. A node which does not have a specific visitor is visited by
+first writing the <code>start_</code> attribute, then visiting its
+children and then writing the <code>end_</code> attribute. If the attributes
+do not exist, they are treated as though they were empty strings.</p>
+
+<p>That code allows most of the HTML elements to LaTeX converters to have no
+code -- only a pair of strings -- while the elements converters which need
+more sophisticated programming can do it via defining a method, which can
+still call the default processor if it needs this functionality.</p>
+
+<p>This pattern is also friendly to subclassing: all a subclass needs to
+do in order to change how an element is handled is to define either a pair
+of class attributes or a method.</p>
+
+<h3>Liberal Use of Reflection</h3>
+
+<p>In the above example of the visitor pattern, registration of the methods
+and attributes is avoided thanks to using the crudest form of reflection
+in Python -- the <code>getattr()</code> function.</p>
+
+<p>In the Lint support tool, more sophisticated reflection is needed when
+it needs to find all methods whose name begins with <code>check_</code>.
+This is done via the Twisted reflection code, built on top of the native
+Python facilities, in the module
+<code class="API">twisted.python.reflect</code>.</p>
+
+<h3>Recursively Searching For Elements</h3>
+
+<p>In the HTML output code, the most common operation is that of getting
+a list of elements which satisfy some property. This is done by one
+primary work-horse function:
+<code class="API">twisted.web.domhelpers.findNodes</code>. This function
+accepts a DOM tree and a function, and returns a list of all elements
+for which this function returns true. Using this, and the fact that Python makes
+it easy to combine functions into boolean combinations, makes analysis
+and modification and of the DOM tree a breeze.</p>
+
+<h2>Lessons Learned</h2>
+
+<h3>Problems With Some Output Formats</h3>
+
+<p>Probably the trickiest thing about non-HTML output formats is escaping.
+The problem comes from two annoying problems which are not really hard
+to solve, but do represent annoyances in the code:</p>
+
+<ul>
+<li>Different characters are escaped differently (for example, <code>\</code>
+ is escaped, in TeX, as <code>$\backslash$</code> while most other
+ characters are escaped as <code>\&lt;char&gt;</code>.</li>
+<li>Escaping depends on context -- special characters should not be escaped
+ at all inside <code>pre</code>, <code>&lt;/&gt;</code> should not be
+ escaped inside <code>code</code> and should be escaped as
+ <code>$&lt;$/$&gt;$</code> outside it.</li>
+</ul>
+
+<p>In Docbook, the sections are nested, so there is only need for
+a <code>title</code> element. However, in HTML only the headers care
+at which level they are. This requires the Docbook converter to keep
+the last header level and when it reaches a new header, to close and open
+enough sections so the header will get to the correct level. While Docbook's
+way may be more <q>correct</q>, it is unfortunate it chose to diverge from
+all other systems here.</p>
+
+<p>Texinfo requires all the sections in a document will have unique names.
+This makes it very inconvenient as both an input and an output format.</p>
+
+<p>Also, differing significance of whitespace in different formats requires that
+all whitespace emitted by lore must be normalized for the particular output
+format being used. Blank lines which have no impact on HTML will trigger
+paragraph breaks in LaTeX.</p>
+
+<h3>Event-based XML Parsing Considered Harmful</h3>
+
+<p>The first version of the LaTeX output generator was using an event-based
+XML parsing engine. It quickly turned out one needs to keep a lot of
+information in stacks and manage many instance variables. For example,
+though XML gets the name of the closing element (even that is arguably
+too much information), it does not get the attributes. In <code>span</code>
+elements, for example, the interesting information is the <code>class</code>
+attribute. Since a-priory, <code>span</code>s might be nested, the class
+needs to keep a stack of attribute collections.</p>
+
+<p>Quite soon, stacks were needed for proper handling of <code>div</code>
+tags and for determining proper quoting formats. Moreover, getting the
+code to function correctly in the face of edge cases, such as cross-references
+inside <code>pre</code> tags, proved to be quite a challenge.</p>
+
+<p>The code was shortened, simplified and became more maintainable when
+it was moved to <code class="API" base="twisted.web">microdom</code>.</p>
+
+<p>We feel that unless there is
+an inherent reason to do XML event-based parsing, then it is much easier
+to read the whole thing into a DOM and then process it. The code is both
+shorter and clearer, and features are much easier to add.</p>
+
+<h3>Allow Easy Modification</h3>
+
+<p>Lore, out of the box, does not attempt to be all things to all people.
+Particularly in the LaTeX output format, there is a lot of room for
+interpretation and personal preferences. Lore chose one specific way, without
+trying to add half a dozen options to tweak it. However, thanks to the
+way it is coded, it is easy to add or modify features to suit individual
+preferences. Many customizations only involve adding or overriding simple data
+attributes to a subclass; more advanced changes require adding or overriding
+methods.</p>
+
+<p>Likewise, the HTML output is built by running several tree-modification
+functions which are independent. Completely different HTML output could
+be build by adding more functions, or not running some of those which
+are being run.</p>
+
+<p>We already know of multiple users that have extended Lore for custom LaTeX
+generation. In each case it was a simple matter of subclassing Lore's LaTeX
+code.</p>
+
+<h3>Reinventing Wheels Can Be Useful</h3>
+
+<p>Documentation generation systems were already a solved problem before Lore
+was written. However, we know of no system with Lore's unique combination of
+features -- in particular, portability, having a directly readable source format
+which is also directly writable in text editors.
+The common wisdom that a documentation generation
+system is a hard sell because it requires people to learn a new language was
+refuted by using an existing language.</p>
+
+<p>Wheel reinvention also occurred in a nearby area -- Twisted's XML support,
+for which Lore is one of the biggest users. Again, the common wisdom was that
+this was a solved problem, with many existing DOM and SAX implementations.
+However, implementation of some features, no implementation of other features
+and API instability have lead the Twisted team to write its own, highly
+pythonic, DOM-like implementation. In
+<code class="API" base="twisted.web">microdom</code>, the aim is to be
+as thin a wrapper over the basic Python wrappers as possible. This feature
+has been used to the full in Lore, where many of the tree manipulations
+would have been much more cumbersome had a standard <q>opaque</q> DOM
+implementation been used. In addition, using
+<code class="API" base="twisted.web">microdom</code> frees Lore from the
+dependence on both Python version and whether PyXML is installed.</p>
+
+<p>For example, <code class="API" base="twisted.web">microdom</code>
+exposes the list of child nodes as a plain Python lists. This means that
+not only all the list operations can be done of it, which could possibly
+be simulated by a list-like object, but that it is possible to
+<em>replace</em> it by our own list. As another example,
+<code class="API" base="twisted.web">microdom</code> allows us to freely
+copy nodes from one DOM tree into another.</p>
+
+<p>Python, as a language well suited to rapid application development,
+acts as a way to make wheel reinvention far from the horrible mistake
+which is portrayed in the common software engineering folklore. Indeed, Python
+makes it easy enough to reinvent wheels that only the best, and easy to
+use, wheels, get reused at all.</p>
+
+<h2>Availability</h2>
+
+<p>Lore can be found in Twisted 1.0.1 and higher, in the
+<code class="API">twisted.lore</code> package. When you install the package,
+the relevant script, <code class="shell">lore</code>,
+should be installed in a sane directory,
+as determined by distutils.</p>
+
+<p>For usage examples, see <code class="shell">admin/release-twisted</code>
+in the Twisted source distribution. It runs the various Lore scripts
+as part of the package build.</p>
+
+<h2>Future Plans</h2>
+
+<h3>More Output Formats</h3>
+
+<p>It would be nice to have the Docbook output fully working. It would also
+be nice to have Texinfo in full working order so that GNU info aficionados could
+read the documents with the info browser. As suggested above, it might
+also be useful to have a way to directly generate PDF output via
+<code>pdflib</code> in order to skip LaTeX.</p>
+
+<p>In addition, another potential output format is to have high-quality
+text output. This is non-trivial, but possibly useful: browsers'
+<q>Save as text</q> feature is usually implemented as an afterthought,
+and hardly uses the flexibility available in the text format to its
+full power. The authors are unaware, for example, of an HTML to text
+converter which uses the underlining with <q>=</q> sign or <q>-</q>
+to indicate a header, or which uses the <code>/slant/</code> or
+<code>*asterisk*</code> conventions to indicate emphasis.</p>
+
+<p>Another output format we are considering is a split-page HTML with
+interlinks, so that long documents can be converted into something
+which is web-friendly. One nice use for that would be in web-based
+presentations.</p>
+
+<h3>Image Conversion</h3>
+
+<p>Currently all images are converted to EPS format. It would be nice to have
+the LaTeX converter try to see if there is already an EPS version, via some
+naming convention, and use that. This would allow better scaling of things like
+Dia diagrams. The versions in bitmap-based formats (such as PNG)
+are impossible to scale, because the text would become unreadable.</p>
+
+<h3>Interface</h3>
+
+<p>Currently, the only interface to Lore is through the command-line, and
+even that is somewhat spotty: for example, the man page parser is not directly
+available via the command line. We hope to remedy that, having at least a full
+suite of command-line tools and possibly graphical wrappers, particularly
+EMACS modes.</p>
+
+<h2>Twisted Integration</h2>
+
+<p>When starting with a historical note, it is only fitting to end
+with a historical note. Since the writing of Lore, Twisted documentation
+is successfully generated by it and distributed in the tarball. It contains
+generated HTML from the HOWTO documents, specifications and man pages.
+It also contains all these documents inside a LaTeX-generated PostScript
+file and PDF file in an easy to print format, suitable for reading on those
+long plane flights or train rides.</p>
+
+<p>Lore is also used to generate pages with consistent headers and footers for
+the twistedmatrix.com web site -- not just the Twisted documentation.
+This is shows the inherent flexibility in Lore's model of being easily
+configurable via an HTML template,
+a feature which none of the major
+document generation systems support for their HTML output.</p>
+
+<h2>Further Resources</h2>
+
+<ul>
+ <li><a
+ href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.0.3/man/lore-man.xhtml"><code
+ class="shell">lore(1)</code> man page</a></li>
+ <li><a
+ href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.0.3/howto/doc-standard">Lore guidelines</a></li>
+ <li><a
+ href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.0.3/howto/lore">Lore HOWTO</a></li>
+ <li><a
+ href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.0.1/examples/example.html">Skeleton Lore document</a></li>
+ <li>The <a
+ href="http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/~checkout~/doc/howto/stylesheet.css?rev=1.16&amp;content-type=text/css&amp;cvsroot=Twisted">stylesheet</a> and <a
+ href="http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/~checkout~/doc/howto/template.tpl?rev=1.6&amp;content-type=text/plain&amp;cvsroot=Twisted">template</a> used by the Twisted documentation</li>
+ <li><a href="http://www.w3.org/TR/xhtml1/">The XHTML specification</a></li>
+ <li><a href="http://www.latex-project.org">LaTeX project home page</a></li>
+ <li><a href="http://www.lyx.org">LyX</a></li>
+ <li><a href="http://www.docbook.org">Docbook</a></li>
+ <li><a href="http://www.python10.com/p10-papers/09/index.htm">Zadka, Moshe and Lefkowitz, Glyph, The Twisted Network Framework, The Tenth International Python Conference Proceedings</a></li>
+</ul>
+
+</body></html>
diff --git a/doc/historic/2003/pycon/pb/pb-client1.py b/doc/historic/2003/pycon/pb/pb-client1.py
new file mode 100755
index 0000000..7814fb7
--- /dev/null
+++ b/doc/historic/2003/pycon/pb/pb-client1.py
@@ -0,0 +1,46 @@
+#! /usr/bin/python
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Client:
+ def connect(self):
+ deferred = pb.getObjectAt("localhost", 8800, 30)
+ deferred.addCallbacks(self.got_obj, self.err_obj)
+ # when the Deferred fires (i.e. when the connection is established and
+ # we receive a reference to the remote object), the 'got_obj' callback
+ # will be run
+
+ def got_obj(self, obj):
+ print "got object:", obj
+ self.server = obj
+ print "asking it to add"
+ def2 = self.server.callRemote("add", 1, 2)
+ def2.addCallbacks(self.add_done, self.err)
+ # this Deferred fires when the method call is complete
+
+ def err_obj(self, reason):
+ print "error getting object", reason
+ self.quit()
+
+ def add_done(self, result):
+ print "addition complete, result is", result
+ print "now trying subtract"
+ d = self.server.callRemote("subtract", 5, 12)
+ d.addCallbacks(self.sub_done, self.err)
+
+ def err(self, reason):
+ print "Error running remote method", reason
+ self.quit()
+
+ def sub_done(self, result):
+ print "subtraction result is", result
+ self.quit()
+
+ def quit(self):
+ print "shutting down"
+ reactor.stop()
+
+c = Client()
+c.connect()
+reactor.run()
diff --git a/doc/historic/2003/pycon/pb/pb-server1.py b/doc/historic/2003/pycon/pb/pb-server1.py
new file mode 100755
index 0000000..c0fb43f
--- /dev/null
+++ b/doc/historic/2003/pycon/pb/pb-server1.py
@@ -0,0 +1,16 @@
+#! /usr/bin/python
+
+from twisted.spread import pb
+import twisted.internet.app
+
+class ServerObject(pb.Root):
+ def remote_add(self, one, two):
+ answer = one + two
+ print "returning result:", answer
+ return answer
+ def remote_subtract(self, one, two):
+ return one - two
+
+app = twisted.internet.app.Application("server1")
+app.listenTCP(8800, pb.BrokerFactory(ServerObject()))
+app.run(save=0)
diff --git a/doc/historic/2003/pycon/pb/pb-slides.py b/doc/historic/2003/pycon/pb/pb-slides.py
new file mode 100755
index 0000000..1a9aea6
--- /dev/null
+++ b/doc/historic/2003/pycon/pb/pb-slides.py
@@ -0,0 +1,240 @@
+#! /usr/bin/python
+
+from slides import Lecture, NumSlide, Slide, Bullet, SubBullet, PRE, URL
+
+class Raw:
+ def __init__(self, title, html):
+ self.title = title
+ self.html = html
+ def toHTML(self):
+ return self.html
+
+class HTML(Raw):
+ def __init__(self, html):
+ self.html = html
+
+server_lore = """<div class="py-listing">
+<pre><span class="py-src-keyword">class</span> <span class="py-src-identifier">ServerObject</span><span class="py-src-op">(</span><span class="py-src-parameter">pb</span><span class="py-src-op">.</span><span class="py-src-parameter">Referenceable</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"></span>
+<span class="py-src-indent"> </span><span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_add</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">one</span><span class="py-src-op">,</span> <span class="py-src-parameter">two</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
+</span><span class="py-src-indent"> </span><span class="py-src-variable">answer</span> <span class="py-src-op">=</span> <span class="py-src-variable">one</span> <span class="py-src-op">+</span> <span class="py-src-variable">two</span><span class="py-src-newline">
+</span> <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;returning result:&quot;</span><span class="py-src-op">,</span> <span class="py-src-variable">answer</span><span class="py-src-newline">
+</span> <span class="py-src-keyword">return</span> <span class="py-src-variable">answer</span><span class="py-src-endmarker"></span></pre>
+<div class="py-caption">Server Code</div>
+</div>
+"""
+
+client_lore = """<div class="py-listing"><pre>
+<span class="py-src-nl"></span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">got_RemoteReference</span><span class="py-src-op">(</span><span class="py-src-parameter">remoteref</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
+</span> <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to add&quot;</span><span class="py-src-newline">
+</span> <span class="py-src-variable">deferred</span> <span class="py-src-op">=</span> <span class="py-src-variable">remoteref</span><span class="py-src-op">.</span><span class="py-src-variable">callRemote</span><span class="py-src-op">(</span><span class="py-src-string">&quot;add&quot;</span><span class="py-src-op">,</span> <span class="py-src-number">1</span><span class="py-src-op">,</span> <span class="py-src-number">2</span><span class="py-src-op">)</span><span class="py-src-newline">
+</span> <span class="py-src-variable">deferred</span><span class="py-src-op">.</span><span class="py-src-variable">addCallbacks</span><span class="py-src-op">(</span><span class="py-src-variable">add_done</span><span class="py-src-op">,</span> <span class="py-src-variable">err</span><span class="py-src-op">)</span><span class="py-src-newline">
+</span> <span class="py-src-comment"># this Deferred fires when the method call is complete
+</span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">add_done</span><span class="py-src-op">(</span><span class="py-src-parameter">result</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
+</span><span class="py-src-indent"> </span><span class="py-src-keyword">print</span> <span class="py-src-string">&quot;addition complete, result is&quot;</span><span class="py-src-op">,</span> <span class="py-src-variable">result</span><span class="py-src-newline">
+</span><span class="py-src-endmarker"></span></pre><div class="py-caption">Client Code</div></div>
+"""
+
+
+# title graphic: PB peanut butter jar, "Twist(ed)" on lid
+lecture = Lecture(
+ "Perspective Broker: Translucent RPC in Twisted",
+ # intro
+ Raw("Title", """
+ <h1>Perspective Broker: Translucent RPC in Twisted</h1>
+ <h2>PyCon 2003</h2>
+ <h2>Brian Warner &lt; warner @ lothar . com &gt; </h2>
+ """),
+
+ Slide("Introduction",
+ Bullet("Overview/definition of RPC"),
+ Bullet("What is Perspective Broker?"),
+ Bullet("How do I use it?"),
+ Bullet("Security Issues"),
+ Bullet("Future Directions"),
+ ),
+
+ Slide("Remote Procedure Calls",
+ Bullet("Action at a distance: separate processes, safely telling each other what to do",
+ SubBullet("Separate memory spaces"),
+ SubBullet("Usually on different machines"),
+ ),
+ Bullet("Frequently called RMI these days: Remote Method Invocation"),
+ Bullet("Three basic parts: Addressing, Serialization, Waiting"),
+ ),
+
+ Slide("Addressing",
+ Bullet("What program are you talking to?",
+ SubBullet("hostname, port number"),
+ SubBullet("Some systems use other namespaces: sunrpc")
+ ),
+ Bullet("Which object in that program?"),
+ Bullet("Which method do you want to run?"),
+ Bullet("Related issues",
+ SubBullet("How do you know what the arguments are?"),
+ SubBullet("(do you care?)"),
+ SubBullet("How do you know what methods are available?"),
+ SubBullet("(do you care?)"),
+ ),
+ ),
+
+ Slide("Serialization",
+ Bullet("What happens to the arguments you send in?"),
+ Bullet("What happens to the results that are returned?",
+ SubBullet("Representation differences: endianness, word length"),
+ SubBullet("Dealing with user-defined types"),
+ ),
+ Bullet("How to deal with references"),
+ ),
+ Slide("The Waiting (is the hardest part)",
+ Bullet("Asynchronous: results come later, or not at all"),
+ Bullet("Need to do other work while waiting"),
+ ),
+
+ Slide("Whither Translucence?",
+ Bullet("Not 'Transparent': don't pretend remote objects are really local",
+ SubBullet("CORBA (in C) does this, makes remote calls look like local calls"),
+ SubBullet("makes it hard to deal with the async nature of RPC"),
+ ),
+ Bullet("Not 'Opaque': make it easy to deal with the differences",
+ SubBullet("Including extra failure modes, delayed results"),
+ ),
+
+ Bullet("Exceptions and Deferreds to the rescue")),
+
+ Slide("Other RPC protocols",
+ Bullet("HTML"),
+ Bullet("XML-RPC"),
+ Bullet("CORBA"),
+ Bullet("when you control both ends of the wire, use PB"),
+ ),
+
+ Raw("Where does PB fit?",
+ """<h2>PB sits on top of <span class=\"py-src-identifier\">twisted.internet</span></h2>
+ <img src=\"twisted-overview.png\" />
+ """),
+
+ Slide("pb.RemoteReference",
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.Referenceable</span>: Object which can be accessed by remote systems."),
+ SubBullet(HTML("Defines methods like <span class=\"py-src-identifier\">remote_foo</span> and <span class=\"py-src-identifier\">remote_bar</span> which can be invoked remotely.")),
+ SubBullet(HTML("Methods without the <span class=\"py-src-identifier\">remote_</span> prefix are local-only.")),
+ ),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.RemoteReference</span>: Used by distant program to invoke methods."),
+ SubBullet(HTML("Offers <span class=\"py-src-identifier\">.callRemote()</span> to trigger remote method on a corresponding <span class=\"py-src-identifier\">pb.Referenceable</span>.")),
+ ),
+ ),
+
+ Raw("Sample code",
+ "<h2>Sample Code</h2>" + server_lore + client_lore),
+ #Slide("Simple Demo"),
+ # "better demo: manhole, or reactor running in another thread"
+
+ #build up from callRemote?
+ Slide("What happens to those arguments?",
+ Bullet("Basic structures should travel transparently",
+ SubBullet("Actually quite difficult in some languages"),
+ ),
+ Bullet("Object graph should remain the same",
+ SubBullet("Serialization context"),
+ SubBullet("(same issues as Pickle)")),
+ Bullet("Instances of user-defined classes require more care",
+ SubBullet("User-controlled unjellying"),)
+ ),
+
+ #serialization (skip banana)
+ Slide("40% More Sandwich Puns Than The Leading Brand",
+ Bullet("twisted.spread: python package holding other modules"),
+ Bullet("PB: remote method invocation"),
+ Bullet("Jelly: mid-level object serialization"),
+ Bullet("Banana: low-level serialization of s-expressions"),
+ Bullet("Taster: security context, decides what may be received"),
+ Bullet("Marmalade: like Jelly, but involves XML, so it's bitter"),
+ Bullet("better than the competition",
+ SubBullet("CORBA: few or no sandwich puns"),
+ SubBullet("XML-RPC: barely pronounceable"),
+ ),
+ ),
+
+ Slide("Jellying objects",
+ Bullet("'Jellying' vs 'Unjellying'"),
+ Bullet("Immutable objects are copied whole"),
+ Bullet("Mutable objects get reference IDs to insure shared references remain shared",
+ SubBullet("(within the same Jellying context)")),
+ ),
+
+ Slide("Jellying instances",
+ Bullet(HTML("User classes inherit from one of the <span class=\"py-src-identifier\">pb.flavor</span> classes")),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.Referenceable</span>: methods can be called remotely")),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.Copyable</span>: contents are selectively copied")),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.Cacheable</span>: contents are copied and kept up to date")),
+ Bullet(HTML("Classes define <span class=\"py-src-identifier\">.getStateToCopy</span> and other methods to restrict exported state")),
+ ),
+
+ Slide("pb.Copyable example",
+ PRE("""class SenderPond(FrogPond, pb.Copyable):
+ def getStateToCopy(self):
+ d = self.__dict__.copy()
+ d['frogsAndToads'] = d['numFrogs'] + d['numToads']
+ del d['numFrogs']
+ del d['numToads']
+ return d
+
+class ReceiverPond(pb.RemoteCopy):
+ def setCopyableState(self, state):
+ self.__dict__ = state
+ self.localCount = 12
+ def count(self):
+ return self.frogsAndToads
+
+pb.setUnjellyableForClass(SenderPond, ReceiverPond)
+""")),
+
+ Slide("Secure Unjellying",
+ Bullet("Pickle has security problems",
+ SubBullet("Pickle will import any module the sender requests."),
+ SubBullet(HTML("2.3 gave up, removed safety checks like <span class=\"py-src-identifier\">__safe_for_unpickling__</span> .")),
+ ),
+ Bullet("Jelly attempts to be safe in the face of hostile clients",
+ SubBullet("All classes rejected by default"),
+ SubBullet(HTML("<span class=\"py-src-identifier\">registerUnjellyable()</span> used to accept safe ones")),
+ SubBullet(HTML("Registered classes define <span class=\"py-src-identifier\">.setCopyableState</span> and others to process remote state")),
+ ),
+ Bullet("Must mark (by subclassing) to transmit"),
+ ),
+
+ Slide("Transformation of references in transit",
+ Bullet("All referenced objects get turned into their counterparts as they go over the wire"),
+ Bullet("References are followed recursively",
+ SubBullet("Sending a reference to a tree of objects will cause the whole thing to be transferred"),
+ SubBullet("(subject to security restrictions)"),
+ ),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.flavors</span> get reference ids"),
+ SubBullet("They are recognized when they return, transformed into the original reference"),
+ SubBullet("Reference ids are scoped to the connection"),
+ SubBullet("One side-effect: no 'third party' references"),
+ ),
+ ),
+
+ Slide("Perspectives: pb.cred and the Identity/Service model",
+ Bullet("A layer to provide common authentication services to Twisted applications"),
+ Bullet(HTML("<span class=\"py-src-identifier\">Identity</span>: named user accounts with passwords")),
+ Bullet(HTML("<span class=\"py-src-identifier\">Service</span>: something a user can request access to")),
+ Bullet(HTML("<span class=\"py-src-identifier\">Perspective</span>: user accessing a service")),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.Perspective</span>: first object, a <span class=\"py-src-identifier\">pb.Referenceable</span> used to access everything else")),
+ ),
+ #picture would help
+
+ Slide("Future directions",
+ Bullet("Other language bindings: Java, elisp, Haskell, Scheme, JavaScript, OCaml"),
+ # donovan is doing the JavaScript port
+ Bullet("Other transports: UDP, Airhook"),
+ Bullet("Componentization"),
+ Bullet("Performance improvements: C extension for Jelly"),
+ Bullet("Refactor addressing model: PB URLs"),
+ ),
+
+ Slide("Questions", Bullet("???")),
+
+ )
+
+lecture.renderHTML("slides", "slide-%02d.html", css="stylesheet.css")
+
diff --git a/doc/historic/2003/pycon/pb/pb.html b/doc/historic/2003/pycon/pb/pb.html
new file mode 100644
index 0000000..95f1ebe
--- /dev/null
+++ b/doc/historic/2003/pycon/pb/pb.html
@@ -0,0 +1,966 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Perspective Broker: <q>Translucent</q> Remote Method calls in Twisted</title>
+ </head>
+
+<body>
+
+<h1>Perspective Broker: <q>Translucent</q> Remote Method calls in Twisted</h1>
+
+<ul>
+<li><a href="http://www.lothar.com">Brian Warner</a>:
+<code>&lt;warner@lothar.com&gt;</code>
+</li>
+</ul>
+
+<h2>Abstract</h2>
+
+<p>One of the core services provided by the Twisted networking framework is
+<q>Perspective Broker</q>, which provides a clean, secure, easy-to-use
+Remote Procedure Call (RPC) mechanism. This paper explains the novel
+features of PB, describes the security model and its implementation, and
+provides brief examples of usage.</p>
+
+<p>PB is used as a foundation for many other services in Twisted, as well as
+projects built upon the Twisted framework. twisted.web servers can delegate
+responsibility for different portions of URL-space by distributing PB
+messages to the object that owns that subspace. twisted.im is an
+instant-messaging protocol that runs over PB. Applications like CVSToys and
+the BuildBot use PB to distribute notices every time a CVS commit has
+occurred. Using Perspective Broker as the RPC layer allows these projects to
+stay focused on the interesting parts.</p>
+
+<p>The PB protocol is not limited to Python. There is a working Java
+implementation available from the Twisted web site, as is an Emacs-Lisp
+version (which can be used to control a PB-enabled application from within
+your editing session, or effectively embed a Python interpreter in Emacs).
+Python's dynamic and introspective nature makes Perspective Broker easier to
+implement (and very convenient to use), but neither are strictly necessary.
+With a set of callback tables and a good dictionary implementation, it would
+be possible to implement the same protocol in C, C++, Perl, or other
+languages.</p>
+
+<h2>Overview</h2>
+
+<h3>Features</h3>
+
+<p>Perspective Broker provides the following basic RPC features.</p>
+
+<ul>
+ <li><strong>remotely-invokable methods</strong>: certain methods (those
+ with names that start with <q>remote_</q>) of
+ <code>pb.Referenceable</code> objects can be invoked by remote clients who
+ hold matching <code>pb.RemoteReference</code> objects.</li>
+
+ <li><strong>transparent, controllable object serialization</strong>: other
+ objects sent through those remote method invocations (either as arguments
+ or in the return value) will be automatically serialized. The data that is
+ serialized, and the way they are represented on the remote side, depends
+ upon which <code>twisted.pb.flavor</code> class they inherit from, and
+ upon overridable methods to get and set state.</li>
+
+ <li><strong>per-connection object ids</strong>: certain objects that are
+ passed by reference are tracked when they are sent over a wire. If the
+ receiver sends back the reference it received, the sender will see their
+ original object come back to them.</li>
+
+ <li><strong>twisted.cred authentication layer</strong>: provides common
+ username/password verification functions. <code>pb.Viewable</code> objects
+ keep a user reference with them, so remotely-invokable methods can find
+ out who invoked them.</li>
+
+ <li><strong>remote exception reporting</strong>: exceptions that occur in
+ remote methods are wrapped in <code>Failure</code> objects and serialized
+ so they can be provided to the caller. All the usual traceback information
+ is available on the invoking side.</li>
+
+ <li><strong>runs over arbitrary byte-pipe transports</strong>: including
+ TCP, UNIX-domain sockets, and SSL connections. UDP support (in the form of
+ Airhook) is being developed.</li>
+
+ <li><strong>numerous sandwich-related puns</strong>: PB, Jelly, Banana,
+ <code>twisted.spread</code>, Marmalade, Tasters, and Flavors. By contrast,
+ CORBA and XML-RPC have few, if any, puns in their naming conventions.</li>
+
+</ul>
+
+<h3>Example</h3>
+
+<p>Here is a simple example of PB in action. The server code creates an
+object that can respond to a few remote method calls, and makes it available
+on a TCP port. The client code connects and runs two methods.</p>
+
+<a href="pb-server1.py" class="py-listing" skipLines="2">pb-server1.py</a>
+<a href="pb-client1.py" class="py-listing" skipLines="2">pb-client1.py</a>
+
+<p>When this is run, the client emits the following progress messages:</p>
+
+<pre class="shell">
+% <em>./pb-client1.py</em>
+got object: &lt;twisted.spread.pb.RemoteReference instance at 0x817cab4&gt;
+asking it to add
+addition complete, result is 3
+now trying subtract
+subtraction result is -7
+shutting down
+</pre>
+
+<p>This example doesn't demonstrate instance serialization, exception
+reporting, authentication, or other features of PB. For more details and
+examples, look at the PB <q>howto</q> docs at <a
+href="http://twistedmatrix.com/documents/howto/">twistedmatrix.com</a>.</p>
+
+<h2>Why <q>Translucent</q> References?</h2>
+
+<p>Remote function calls are not the same as local function calls. Remote
+calls are asynchronous. Data exchanged with a remote system may be
+interpreted differently depending upon version skew between the two systems.
+Method signatures (number and types of parameters) may differ. More failure
+modes are possible with RPC calls than local ones.</p>
+
+<p><q>Transparent</q> RPC systems attempt to hide these differences, to make
+remote calls look the same as local ones (with the noble intention of making
+life easier for programmers), but the differences are real, and hiding them
+simply makes them more difficult to deal with. PB therefore provides
+<q>translucent</q> method calls: it exposes these differences, but offers
+convenient mechanisms to handle them. Python's flexible object model and
+exception handling take care of part of the problem, while Twisted's
+Deferred class provides a clean way to deal with the asynchronous nature of
+RPC.</p>
+
+<h3>Asynchronous Invocation</h3>
+
+<p>A fundamental difference between local function calls and remote ones is
+that remote ones are always performed asynchronously. Local function calls
+are generally synchronous (at least in most programming languages): the
+caller is blocked until the callee finishes running and possibly returns a
+value. Local functions which might block (loosely defined as those which
+would take non-zero or indefinite time to run on infinitely fast hardware)
+are usually marked as such, and frequently provide alternative APIs to run
+in an asynchronous manner. Examples of blocking functions are
+<code>select()</code> and its less-generalized cousins:
+<code>sleep()</code>, <code>read()</code> (when buffers are empty), and
+<code>write()</code> (when buffers are full).</p>
+
+<p>Remote function calls are generally assumed to take a long time. In
+addition to the network delays involved in sending arguments and receiving
+return values, the remote function might itself be blocking.</p>
+
+<p><q>Transparent</q> RPC systems, which pretend that the remote system is
+really local, usually offer only synchronous calls. This prevents the
+program from getting other work done while the call is running, and causes
+integration problems with GUI toolkits and other event-driven
+frameworks.</p>
+
+<h3>Failure Modes</h3>
+
+<p>In addition to the usual exceptions that might be raised in the course of
+running a function, remotely invoked code can cause other errors. The
+network might be down, the remote host might refuse the connection (due to
+authorization failures or resource-exhaustion issues), the remote end might
+have a different version of the code and thus misinterpret serialized
+arguments or return a corrupt response. Python's flexible exception
+mechanism makes these errors easy to report: they are just more exceptions
+that could be raised by the remote call. In other languages, this requires a
+special API to report failures via a different path than the normal
+response.</p>
+
+<h3>Deferreds to the rescue</h3>
+
+<p>In PB, Deferreds are used to handle both the asynchronous nature of the
+method calls and the various kinds of remote failures that might occur. When
+the method is invoked, PB returns a Deferred object that will be fired
+later, when the response (success or failure) is received from the remote
+end. The caller (the one who invoked <code>callRemote</code>) is free to
+attach callback and errback handlers to the Deferred. If an exception is
+raised (either by the remote code or a network failure during processing),
+the errback will be run with the wrapped exception. If the function
+completes normally, the callback is run.</p>
+
+<p>By using Deferreds, the invoking program can get other work done while it
+is waiting for the results. Failure is handled just as cleanly as
+success.</p>
+
+<p>In addition, the remote method can itself return a <code>Deferred</code>
+instead of an actual return value. When that <code>Deferreds</code> fires,
+the data given to the callback will be serialized and returned to the
+original caller. This allows the remote server to perform other work as
+well, putting off the answer until one is available.</p>
+
+
+<h2>Calling Remote Methods</h2>
+
+<p>Perspective Broker is first and foremost a mechanism for remote method
+calls: doing something to a local object which causes a method to get run on
+a distant one. The process making the request is usually called the
+<q>client</q>, and the process which hosts the object that actually runs the
+method is called the <q>server</q>. Note, however, that method requests can
+go in either direction: instead of distinguishing <q>client</q> and
+<q>server</q>, it makes more sense to talk about the <q>sender</q> and
+<q>receiver</q> for any individual method call. PB is symmetric, and the
+only real difference between the two ends is that one initiated the original
+TCP connection and the other accepted it.</p>
+
+<p>With PB, the local object is an instance of
+<code>twisted.spread.pb.RemoteReference</code>, and you <q>do something</q>
+to it by calling its <code>.callRemote</code> method. This call accepts a
+method name and an argument list (including keyword arguments). Both are
+serialized and sent to the receiving process, and the call returns a
+<code>Deferred</code>, to which you can add callbacks. Those callbacks will
+be fired later, when the response returns from the remote end.</p>
+
+<p>That local RemoteReference points at a
+<code>twisted.spread.pb.Referenceable</code> object living in the other
+program (or one of the related callable flavors). When the request comes
+over the wire, PB constructs a method name by prepending
+<code>remote_</code> to the name requested by the remote caller. This method
+is looked up in the <code>pb.Referenceable</code> and invoked. If an
+exception is raised (including the <code>AttributeError</code> that results
+from a bad method name), the error is wrapped in a <code>Failure</code>
+object and sent back to the caller. If it succeeds, the result is serialized
+and sent back.</p>
+
+<p>The caller's Deferred will either have the callback run (if the method
+completed normally) or the errback run (if an exception was raised). The
+Failure object given to the errback handler allows a full stack trace to be
+displayed on the calling end.</p>
+
+<p>For example, if the holder of the <code>RemoteReference</code> does <code
+class="python">rr.callRemote("foo", 1, 3)</code>, the corresponding
+<code>Referenceable</code> will be invoked with <code
+class="python">r.remote_foo(1, 3)</code>. A <code>callRemote</code> of
+<q><code>bar</code></q> would invoke <code>remote_bar</code>, etc.</p>
+
+<h3>Obtaining other references</h3>
+
+<p>Each <code>pb.RemoteReference</code> object points to a
+<code>pb.Referenceable</code> instance in some other program. The first such
+reference must be acquired with a bootstrapping function like
+<code>pb.getObjectAt</code>, but all subsequent ones are created when a
+<code>pb.Referenceable</code> is sent as an argument to (or a return value
+from) a remote method call.</p>
+
+<p>When the arguments or return values contain references to other objects,
+the object that appears on the other side of the wire depends upon the type
+of the referred object. Basic types are simply copied: a dictionary of lists
+will appear as a dictionary of lists, with internal references preserved on
+a per-method-call basis (just as Pickle will preserve internal references
+for everything pickled at the same time). Class instances are restricted,
+both to avoid confusion and for security reasons.</p>
+
+<h3>Transferring Instances</h3>
+
+<p>PB only allows certain kinds of objects to be transferred to and from
+remote processes. Most of these restrictions are implemented in the <a
+href="#jelly">Jelly</a> serialization layer, described below. In general, to
+send an object over the wire, it must either be a basic python type (list,
+dictionary, etc), or an instance of a class which is derived from one of the
+four basic <em>PB Flavors</em>: <code>Referenceable</code>,
+<code>Viewable</code>, <code>Copyable</code>, and <code>Cacheable</code>.
+Each flavor has methods which define how the object should be treated when
+it needs to be serialized to go over the wire, and all have related classes
+that are created on the remote end to represent them.</p>
+
+<p>There are a few kinds of callable classes. All are represented on the
+remote system with <code>RemoteReference</code> instances.
+<code>callRemote</code> can be used on these RemoteReferences, causing
+methods with various prefixes to be invoked.</p>
+
+<table border="1">
+ <tr>
+ <th>Local Class</th>
+ <th>Remote Representation</th>
+ <th>method prefix</th>
+ </tr>
+ <tr>
+ <td><code>Referenceable</code></td>
+ <td><code>RemoteReference</code></td>
+ <td><code>remote_</code></td>
+ </tr>
+ <tr>
+ <td><code>Viewable</code></td>
+ <td><code>RemoteReference</code></td>
+ <td><code>view_</code></td>
+ </tr>
+</table>
+
+<p><code>Viewable</code> (and the related <code>Perspective</code> class)
+are described later (in <a href="#authorization">Authorization</a>). They
+provide a secure way to let methods know <em>who</em> is calling them. Any
+time a <code>Referenceable</code> (or <code>Viewable</code>) is sent over
+the wire, it will appear on the other end as a <code>RemoteReference</code>.
+If any of these references are sent back to the system they came from, they
+emerge from the round trip in their original form.</p>
+
+<p>Note that RemoteReferences cannot be sent to anyone else (there are no
+<q>third-party references</q>): they are scoped to the connection between
+the holder of the <code>Referenceable</code> and the holder of the
+<code>RemoteReference</code>. (In fact, the <code>RemoteReference</code> is
+really just an index into a table maintained by the owner of the original
+<code>Referenceable</code>).</p>
+
+<p>There are also two data classes. To send an instance over the wire, it
+must belong to a class which inherits from one of these.</p>
+
+<table border="1">
+ <tr>
+ <th>Local Class</th>
+ <th>Remote Representation</th>
+ </tr>
+ <tr>
+ <td><code>Copyable</code></td>
+ <td><code>RemoteCopy</code></td>
+ </tr>
+ <tr>
+ <td><code>Cacheable</code></td>
+ <td><code>RemoteCache</code></td>
+ </tr>
+</table>
+
+<h3>pb.Copyable</h3>
+<a name="pb.Copyable"></a>
+
+<p><code>Copyable</code> is used to allow class instances to be sent over
+the wire. <code>Copyable</code>s are copy-by-value, unlike
+<code>Referenceable</code>s which are copy-by-reference.
+<code>Copyable</code> objects have a method called
+<code>getStateToCopy</code> which gets to decide how much of the object
+should be sent to the remote system: the default simply copies the whole
+<code>__dict__</code>. The receiver must register a <code>RemoteCopy</code>
+class for each kind of <code>Copyable</code> that will be sent to it: this
+registration (described later in <a href="#unjellyableRegistry">Representing
+Instances</a>) maps class names to actual classes. Apart from being a
+security measure (it emphasizes the fact that the process is receiving data
+from an untrusted remote entity and must decide how to interpret it safely),
+it is also frequently useful to distinguish a copy of an object from the
+original by holding them in different classes.</p>
+
+<p><code>getStateToCopy</code> is frequently used to remove attributes that
+would not be meaningful outside the process that hosts the object, like file
+descriptors. It also allows shared objects to hold state that is only
+available to the local process, including passwords or other private
+information. Because the default serialization process recursively follows
+all references to other objects, it is easy to accidentally send your entire
+program to the remote side. Explicitly creating the state object (creating
+an empty dictionary, then populating it with only the desired instance
+attributes) is a good way to avoid this.</p>
+
+<p>The fact that PB will refuse to serialize objects that are neither basic
+types nor explicitly marked as being transferable (by subclassing one of the
+pb.flavors) is another way to avoid the <q>don't tug on that, you never know
+what it might be attached to</q> problem. If the object you are sending
+includes a reference to something that isn't marked as transferable, PB will
+raise an InsecureJelly exception rather than blindly sending it anyway (and
+everything else it references).</p>
+
+<p>Finally, note that <code>getStateToCopy</code> is distinct from the
+<code>__getstate__</code> method used by Pickle, and they can return
+different values. This allows objects to be persisted (across time)
+differently than they are transmitted (across [memory]space).</p>
+
+<h3>pb.Cacheable</h3>
+<a name="pb.Cacheable"></a>
+
+<p><code>Cacheable</code> is a variant of <code>Copyable</code> which is
+used to implement remote caches. When a <code>Cacheable</code> is sent
+across a wire, a method named <code>getStateToCacheAndObserveFor</code> is
+used to simultaneously get the object's current state and to register an
+<q>Observer</q> which lives next to the <code>Cacheable</code>. The Observer
+is effectively a <code>RemoteReference</code> that points at the remote
+cache. Each time the cached object changes, it uses its Observers to tell
+all the remote caches about the change. The <q>setter</q> methods can just
+call <code class="python">observer.callRemote("setFoo", newvalue)</code> for
+all their observers.</p>
+
+<p>On the remote end, a <code>RemoteCache</code> object is created, which
+populates the original object's state just as <code>RemoteCopy</code> does.
+When changes are made, the Observers remotely invoke methods like
+<code>observe_setFoo</code> in the <code>RemoteCache</code> to perform the
+updates.</p>
+
+<p>As <code>RemoteCache</code> objects go away, their Observers go away too,
+and call <code>stoppedObserving</code> so they can be removed from the
+list.</p>
+
+<p>The PB <a href="http://twistedmatrix.com/documents/howto/"
+><q>howto</q> docs</a> have more information and complete examples of both
+<code>pb.Copyable</code> and <code>pb.Cacheable</code>.</p>
+
+
+<h2>Authorization</h2>
+<a name="authorization"></a>
+
+<p>As a framework, Perspective Broker (indeed, all of Twisted) was built
+from the ground up. As multiple use cases became apparent, common
+requirements were identified, code was refactored, and layers were developed
+to cleanly serve the needs of all <q>customers</q>. The twisted.cred layer
+was created to provide authorization services for PB as well as other
+Twisted services, like the HTTP server and the various instant messaging
+protocols. The abstract notions of identity and authority it uses are
+intended to match the common needs of these various protocols: specific
+applications can always use subclasses that are more appropriate for their
+needs.</p>
+
+<h3>Identity and Perspectives</h3>
+
+<p>In twisted.cred, <q>Identities</q> are usernames (with passwords),
+represented by <code>Identity</code> objects. Each identity has a
+<q>keyring</q> which authorizes it to access a set of objects called
+<q>Perspectives</q>. These perspectives represent accounts or other
+capabilities; each belongs to a single <q>Service</q>. There may be multiple
+Services in a single application; in fact the flexible nature of Twisted
+makes this easy. An HTTP server would be a Service, and an IRC server would
+be another one.</p>
+
+<p>As an example, a login service might have perspectives for Alice, Bob,
+and Charlie, and there might also be an Admin perspective. Alice has admin
+capabilities. In addition, let us say the same application has a chat
+service with accounts for each person (but no special administrator
+account).</p>
+
+<p>So, in this example, Alice's keyring gives her access to three
+perspectives: login/Alice, login/Admin, and chat/Alice. Bob only gets two:
+login/Bob and chat/Bob. <code>Perspective</code> objects have names and
+belong to <code>Service</code> objects, but the
+<code>Identity.keyring</code> is a dictionary indexed by (serviceName,
+perspectiveName) pairs. It uses names instead of object references because
+the <code>Perspective</code> object might be created on demand. The keys
+include the service name because Perspective names are scoped to a single
+service.</p>
+
+<h3>pb.Perspective</h3>
+
+<p>The PB-specific subclass of the generic <code>Perspective</code> class is
+also capable of remote execution. The login process results in the
+authorized client holding a special kind of <code>RemoteReference</code>
+that will allow it to invoke <code>perspective_</code> methods on the
+matching <code>pb.Perspective</code> object. In PB applications that use the
+<code>twisted.cred</code> authorization layer, clients get this reference
+first. The client is then dependent upon the Perspective to provide
+everything else, so the Perspective can enforce whatever security policy it
+likes.</p>
+
+<p>(Note that the <code>pb.Perspective</code> class is not actually one of
+the serializable PB flavors, and that instances of it cannot be sent
+directly over the wire. This is a security feature intended to prevent users
+from getting access to somebody else's <code>Perspective</code> by mistake,
+perhaps when a <q>list all users</q> command sends back an object which
+includes references to other Perspectives.)</p>
+
+<p>PB provides functions to perform a challenge-response exchange in which
+the remote client proves their identity to get that <code>Perspective</code>
+reference. The <code>Identity</code> object holds a password and uses an MD5
+hash to verify that the remote user knows the password without sending it in
+cleartext over the wire. Once the remote user has proved their identity,
+they can request a reference to any <code>Perspective</code> permitted by
+their <code>Identity</code>'s keyring.</p>
+
+<p>There are twisted.cred functions (twisted.enterprise.dbcred) which can
+pull user information out of a database, and it is easy to create modules
+that could check /etc/passwd or LDAP instead. Authorization can then be
+centralized through the Perspective object: each object that is accessible
+remotely can be created with a pointer to the local Perspective, and objects
+can ask that Perspective whether the operation is allowed before performing
+method calls.</p>
+
+<p>Most clients use a helper function called <code>pb.connect()</code> to
+get the first Perspective reference: it takes all the necessary identifying
+information (host, port, username, password, service name, and perspective
+name) and returns a <code>Deferred</code> that will be fired when the
+<code>RemoteReference</code> is available. (This may change in the future:
+there are plans afoot to use a URL-like scheme to identify the Perspective,
+which will probably mean a new helper function).</p>
+
+<h3>Viewable</h3>
+
+<p>There is a special kind of <code>Referenceable</code> called
+<code>pb.Viewable</code>. Its remote methods (all named <code>view_</code>)
+are called with an extra argument that points at the
+<code>Perspective</code> the client is using. This allows the same
+<code>Referenceable</code> to be shared among multiple clients while
+retaining the ability to treat those clients differently. The methods can
+check with the Perspective to see if the request should be allowed, and can
+use per-client information in processing the request.</p>
+
+<!-- XXX: it would be nice to provide some examples of typical Perspective
+use cases: static pre-defined Perspectives, DB lookup, anonymous access. But
+they would be pretty big, and are probably more appropriate for the
+pb-cred.html HOWTO doc -->
+
+
+<h2>PB Design: Object Serialization</h2>
+
+<p>Fundamental to any calling convention, whether ABI or RPC, is how
+arguments and return values are passed from caller to callee and back. RPC
+systems require data to be turned into a form which can be delivered through
+a network, a process usually known as serialization. Sharing complex types
+(references and class instances) with a remote system requires more care:
+references should all point to the same thing (even though the object being
+referenced might live on either end of the connection), and allowing a
+remote user to create arbitrary class instances in your memory space is a
+security risk that must be controlled.</p>
+
+<p>PB uses its own serialization scheme called <q>Jelly</q>. At the bottom
+end, it uses s-expressions (lists of numbers and strings) to represent the
+state of basic types (lists, dictionaries, etc). These s-expressions are
+turned into a bytestream by the <q>Banana</q> layer, which has an optional C
+implementation for speed. Unserialization for higher-level objects is driven
+by per-class <q>jellyier</q> objects: this flexibility allows PB to offer
+inheritable classes for common operations. <code>pb.Referenceable</code> is
+a class which is serialized by sending a reference to the remote end that
+can be used to invoke remote methods. <code>pb.Copyable</code> is a class
+which creates a new object on the remote end, with methods that the
+developer can override to control how much state is sent or accepted.
+<code>pb.Cacheable</code> sends a full copy the first time it is exchanged,
+but then sends deltas as the object is modified later.</p>
+
+<p>Objects passed over the wire get to decide for themselves how much
+information is actually passed to the remote system. Copy-by-reference
+objects are given a per-connection ID number and stashed in a local
+dictionary. Copy-by-value objects may send their entire
+<code>__dict__</code>, or some subset thereof. If the remote method returns
+a referenceable object that was given to it earlier (either in the same RPC
+call or an earlier one), PB sends the ID number over the wire, which is
+looked up and turned into a proper object reference upon receipt. This
+provides one-sided reference transparency: one end sees objects coming and
+going through remote method calls in exactly the same fashion as through
+local calls. Those references are only capable of very specific operations;
+PB does not attempt to provide full object transparency. As discussed later,
+this is instrumental to security.</p>
+
+<h3>Banana and s-expressions</h3>
+
+<p>The <q>Banana</q> low-level serialization layer converts s-expressions
+which represent basic types (numbers, strings, and lists of numbers,
+strings, or other lists) to and from a bytestream. S-expressions are easy to
+encode and decode, and are flexible enough (when used with a set of tokens)
+to represent arbitrary objects. <q>cBanana</q> is a C extension module which
+performs the encode/decode step faster than the native python
+implementation.</p>
+
+<p>Each s-expression element is converted into a message with two or three
+components: a header, a type marker, and an optional body (used only for
+strings). The header is a number expressed in base 128. The type marker is a
+single byte with the high bit set, that both terminates the header and
+indicate the type of element this message describes (number, list-start,
+string, or tokenized string).</p>
+
+<p>When a connection is first established, a list of strings is sent to
+negotiate the <q>dialect</q> of Banana being spoken. The first dialect known
+to both sides is selected. Currently, the dialect is only used to select a
+list of string tokens that should be specially encoded (for performance),
+but subclasses of Banana could use self.currentDialect to influence the
+encoding process in other ways.</p>
+
+<p>When Banana is used for PB (by negotiating the <q>pb</q> dialect), it has
+a list of 30ish strings that are encoded into two-byte sequences instead of
+being sent as generalized string messages. These string tokens are used to
+mark complex types (beyond the simple lists, strings, and numbers provided
+natively by Banana) and other objects Jelly needs to do its job.</p>
+
+<h3>Jelly</h3>
+<a name="jelly"></a>
+
+<p><code>Jelly</code> handles object serialization. It fills a similar role
+to the standard Pickle module, but has design goals of security and
+portability (especially to other languages) where Pickle favors efficiency
+of representation. In addition, Jelly serializes objects into s-expressions
+(lists of tokens, strings, numbers, and other lists), and lets Banana do the
+rest, whereas Pickle goes all the way down to a bytestream by itself.</p>
+
+<p>Basic python types (apart from strings and numbers, which Banana can
+handle directly) are generally turned into lists with a type token as the
+first element. For example, a python dictionary is turned into a list that
+starts with the string token <q>dictionary</q> and continues with elements
+that are lists of [key, value] pairs. Modules, classes, and methods are all
+transformed into s-expressions that refer to the relevant names. Instances
+are represented by combining the class name (a string) with an arbitrary
+state object (which is usually a dictionary).</p>
+
+<p>Much of the rest of Jelly has to do with safely handling class instances
+(as opposed to basic Python types) and dealing with references to shared
+objects.</p>
+
+<h4>Tracking shared references</h4>
+
+<p>Mutable types are serialized in a way that preserves the identity between
+the same object referenced multiple times. As an example, a list with four
+elements that all point to the same object must look the same on the remote
+end: if it showed up as a list pointing to four independent objects (even if
+all the objects had identical states), the resulting list would not behave
+in the same way as the original. Changing <code>newlist[0]</code> would not
+modify <code>newlist[1]</code> as it ought to.</p>
+
+<p>Consequently, when objects which reference mutable types are serialized,
+those references must be examined to see if they point to objects which have
+already been serialized in the same session. If so, an object id tag of some
+sort is put into the bytestream instead of the complete object, indicating
+that the deserializer should use a reference to a previously-created object.
+This also solves the issue of recursive or circular references: the first
+appearance of an object gets the full state, and all subsequent ones get a
+reference to it.</p>
+
+<p>Jelly manages this reference tracking through an internal
+<code>_Jellier</code> object (in particular through the <code>.cooked</code>
+dictionary). As objects are serialized, their <code>id</code> values are
+stashed. References to those objects that occur after jellying has started
+can be replaced with a <q>dereference</q> marker and the object id.</p>
+
+<p>The scope of this <code>_Jellier</code> object is limited to a single
+call of the <code>jelly</code> function, which in general corresponds to a
+single remote method call. The argument tuple is jellied as a single object
+(a tuple), so different arguments to the same method will share referenced
+objects<span class="footnote">Actually, PB currently jellies the list
+arguments in a separate tuple from the keyword arguments. This issue is
+currently being examined and may be changed in the future</span>, but
+arguments of separate methods will not share them. To do more complex
+caching and reference tracking, certain PB <q>flavors</q> (see below)
+override their <code>jellyFor</code> method to do more interesting things.
+In particular, <code>pb.Referenceable</code> objects have code to insure
+that one which makes a round trip will come back as a reference to the same
+object that was originally sent.</p>
+
+<p>An exception to this <q>one-call scope</q> is provided: if the
+<code>Jellier</code> is created with a <code>persistentStore</code> object,
+all class instances will be passed through it first, and it has the
+opportunity to return a <q>persistent id</q>. If available, this id is
+serialized instead of the object's state. This would allow object references
+to be shared between different invocations of <code>jelly</code>. However,
+PB itself does not use this technique: it uses overridden
+<code>jellyFor</code> methods to provide per-connection shared
+references.</p>
+
+<h4>Representing Instances</h4>
+<a name="unjellyableRegistry"></a>
+
+<p>Each class gets to decide how it should be represented on a remote
+system. Sending and receiving are separate actions, performed in separate
+programs on different machines. So, to be precise, each class gets to decide
+two things. First, they get to specify how they should be sent to a remote
+client: what should happen when an instance is serialized (or <q>jellied</q>
+in PB lingo), what state should be recorded, what class name should be sent,
+etc. Second, the receiving program gets to specify how an incoming object
+that claims to be an instance of some class should be treated: whether it
+should be accepted at all, if so what class should be used to create the new
+object, and how the received state should be used to populate that
+object.</p>
+
+<p>A word about notation: in Perspective Broker parlance, <q>to jelly</q> is
+used to describe the act of turning an object into an s-expression
+representation (serialization, or at least most of it). Therefore the
+reverse process, which takes an s-expression and turns it into a real python
+object, is described with the verb <q>to unjelly</q>. </p>
+
+<h4>Jellying Instances</h4>
+
+<p>Serializing instances is fairly straightforward. Classes which inherit
+from <code>Jellyable</code> provide a <code>jellyFor</code> method, which
+acts like <code>__getstate__</code> in that it should return a serializable
+representation of the object (usually a dictionary). Other classes are
+checked with a <code>SecurityOptions</code> instance, to verify that they
+are safe to be sent over the wire, then serialized by using their
+<code>__getstate__</code> method (or their <code>__dict__</code> if no such
+method exists). User-level classes always inherit from one of the PB
+<q>flavors</q> like <code>pb.Copyable</code> (all of which inherit from
+<code>Jellyable</code>) and use <code>jellyFor</code>; the
+<code>__getstate__</code> option is only for internal use.</p>
+
+<!-- should we mention persistentStore here? Nothing uses it, so no. Besides
+it was already hinted at in 'tracking shared references' above. -->
+
+<h4>Secure Unjellying</h4>
+
+<p>Unjellying (for instances) is triggered by the receipt of an s-expression
+with the <q>instance</q> tag. The s-expression has two elements: the name of
+the class, and an object (probably a dictionary) which holds the instance's
+state. At that point in time, the receiving program does not know what class
+should be used: it is certainly <em>not</em> safe to simply do an
+<code>import</code> of the classname requested by the sender. That
+effectively allows a remote entity to run arbitrary code on your system.
+</p>
+
+<p>There are two techniques used to control how instances are unjellied. The
+first is a <code>SecurityOptions</code> instance which gets to decide
+whether the incoming object should accepted or not. It is said to
+<q>taste</q> the incoming type before really trying to unserialize it. The
+default taster accepts all basic types but no classes or instances.</p>
+
+<p>If the taster decides that the type is acceptable, Jelly then turns to
+the <code>unjellyableRegistry</code> to determine exactly <em>how</em> to
+deserialize the state. This is a table that maps received class names names
+to unserialization routines or classes.</p>
+
+<p>The receiving program must register the classes it is willing to accept.
+Any attempts to send instances of unregistered classes to the program will
+be rejected, and an InsecureJelly exception will be sent back to the sender.
+If objects should be represented by the same class in both the sender and
+receiver, and if the class is defined by code which is imported into both
+programs (an assumption that results in many security problems when it is
+violated), then the shared module can simply claim responsibility as the
+classes are defined:</p>
+
+<pre class="python">
+class Foo(pb.RemoteCopy):
+ def __init__(self):
+ # note: __init__ will *not* be called when creating RemoteCopy objects
+ pass
+ def __getstate__(self):
+ return foo
+ def __setstate__(self, state):
+ self.stuff = state.stuff
+setUnjellyableForClass(Foo, Foo)
+</pre>
+
+<p>In this example, the first argument to
+<code>setUnjellyableForClass</code> is used to get the fully-qualified class
+name, while the second defines which class will be used for unjellying.
+<code>setUnjellyableForClass</code> has two functions: it informs the
+<q>taster</q> that instances of the given class are safe to receive, and it
+registers the local class that should be used for unjellying.</p>
+
+
+<h3>Broker</h3>
+
+<p>The <code>Broker</code> class manages the actual connection to a remote
+system. <code>Broker</code> is a <q>Protocol</q> (in Twisted terminology),
+and there is an instance for each socket over which PB is being spoken.
+Proxy objects like <code>pb.RemoteReference</code>, which are associated
+with another object on the other end of the wire, all know which Broker they
+must use to get to their remote counterpart. <code>pb.Broker</code> objects
+implement distributed reference counts, manage per-connection object IDs,
+and provide notification when references are lost (due to lost connections,
+either from network problems or program termination).</p>
+
+<h4>PB over Jelly</h4>
+
+<p>Perspective Broker is implemented by sending Jellied commands over the
+connection. These commands are always lists, and the first element of the
+list is always a command name. The commands are turned into
+<code>proto_</code>-prefixed method names and executed in the Broker object.
+There are currently 9 such commands. Two (<code>proto_version</code> and
+<code>proto_didNotUnderstand</code>) are used for connection negotiation.
+<code>proto_message</code> is used to implement remote method calls, and is
+answered by either <code>proto_answer</code> or
+<code>proto_error</code>.</p>
+
+<p><code>proto_cachemessage</code> is used by Observers (see <a
+href="#pb.Copyable">pb.Copyable</a>) to notify their
+<code>RemoteCache</code> about state updates, and behaves like
+<code>proto_message</code>. <a href="#pb.Cacheable">pb.Cacheable</a> also
+uses <code>proto_decache</code> and <code>proto_uncache</code> to manage
+reference counts of cached objects.</p>
+
+<p>Finally, <code>proto_decref</code> is used to manage reference counts on
+<code>RemoteReference</code> objects. It is sent when the
+<code>RemoteReference</code> goes away, so that the holder of the original
+<code>Referenceable</code> can free that object.</p>
+
+<h4>Per-Connection ID Numbers</h4>
+
+<p>Each time a <code>Referenceable</code> is sent across the wire, its
+<code>jellyFor</code> method obtains a new unique <q>local ID</q> (luid) for
+it, which is a simple integer that refers to the original object. The
+Broker's <code>.localObjects{}</code> and <code>.luids{}</code> tables
+maintain the <q>luid</q>-to-object mapping. Only this ID number is sent to
+the remote system. On the other end, the object is unjellied into a
+<code>RemoteReference</code> object which remembers its Broker and the luid
+it refers to on the other end of the wire. Whenever
+<code>callRemote()</code> is used, it tells the Broker to send a message to
+the other end, including the luid value. Back in the original process, the
+luid is looked up in the table, turned into an object, and the named method
+is invoked.</p>
+
+<p>A similar system is used with Cacheables: the first time one is sent, an
+ID number is allocated and recorded in the
+<code>.remotelyCachedObjects{}</code> table. The object's state (as returned
+by <code>getStateToCacheAndObserveFor()</code>) and this ID number are sent
+to the far end. That side uses <code>.cachedLocallyAs()</code> to find the
+local <code>CachedCopy</code> object, and tracks it in the Broker's
+<code>.locallyCachedObjects{}</code> table. (Note that to route state
+updates to the right place, the Broker on the <code>CachedCopy</code> side
+needs to know where it is. The same is not true of
+<code>RemoteReference</code>s: nothing is ever sent <em>to</em> a
+<code>RemoteReference</code>, so its Broker doesn't need to keep track of
+it).</p>
+
+<p>Each remote method call gets a new <code>requestID</code> number. This
+number is used to link the request with the response. All pending requests
+are stored in the Broker's <code>.waitingForAnswers{}</code> table until
+they are completed by the receipt of a <code>proto_answer</code> or
+<code>proto_error</code> message.</p>
+
+<p>The Broker also provides hooks to be run when the connection is lost.
+Holders of a <code>RemoteReference</code> can register a callback with
+<code>.notifyOnDisconnect()</code> to be run when the process which holds
+the original object goes away. Trying to invoke a remote method on a
+disconnected broker results in an immediate <code>DeadReferenceError</code>
+exception.</p>
+
+<h4>Reference Counting</h4>
+
+<p>The Broker on the <code>Referenceable</code> end of the connection needs
+to implement distributed reference counting. The fact that a remote end
+holds a <code>RemoteReference</code> should prevent the
+<code>Referenceable</code> from being freed. To accomplish this, The
+<code>.localObjects{}</code> table actually points at a wrapper object
+called <code>pb.Local</code>. This object holds a reference count in it that
+is incremented by one for each <code>RemoteReference</code> that points to
+the wrapped object. Each time a Broker serializes a
+<code>Referenceable</code>, that count goes up. Each time the distant
+<code>RemoteReference</code> goes away, the remote Broker sends a
+<code>proto_decref</code> message to the local Broker, and the count goes
+down. When the count hits zero, the <code>Local</code> is deleted, allowing
+the original <code>Referenceable</code> object to be released.</p>
+
+
+<h2>Security</h2>
+
+<p>Insecurity in network applications comes from many places. Most can be
+summarized as trusting the remote end to behave in a certain way.
+Applications or protocols that do not have a way to verify their assumptions
+may act unpredictably when the other end misbehaves; this may result in a
+crash or a remote compromise. One fundamental assumption that most RPC
+libraries make when unserializing data is that the same library is being
+used at the other end of the wire to generate that data. Developers put so
+much time into making their RPC libraries work <strong>at all</strong> that
+they usually assume their own code is the only thing that could possibly
+provide the input. A safer design is to assume that the input will almost
+always be corrupt, and to make sure that the program survives anyway.</p>
+
+<h3>Controlled Object serialization</h3>
+
+<p>Security is a primary design goal of PB. The receiver gets final say as
+to what they will and will not accept. The lowest-level serialization
+protocol (<q>Banana</q>) is simple enough to validate by inspection, and
+there are size limits imposed on the actual data received to prevent
+excessive memory consumption. Jelly is willing to accept basic data types
+(numbers, strings, lists and dictionaries of basic types) without question,
+as there is no dangerous code triggered by their creation, but Class
+instances are rigidly controlled. Only subclasses of the basic PB flavors
+(<code>pb.Copyable</code>, etc) can be passed over the wire, and these all
+provide the developer with ways to control what state is sent and accepted.
+Objects can keep private data on one end of the connection by simply not
+including it in the copied state.</p>
+
+<p>Jelly's refusal to serialize objects that haven't been explicitly marked
+as copyable helps stop accidental security leaks. Seeing the
+<code>pb.Copyable</code> tag in the class definition is a flag to the
+developer that they need to be aware of what parts of the class will be
+available to a remote system and which parts are private. Classes without
+those tags are not an issue: the mere act of <em>trying</em> to export them
+will cause an exception. If Jelly tried to copy arbitrary classes, the
+security audit would have to look into <em>every</em> class in the
+system.</p>
+
+<h3>Controlled Object Unserialization</h3>
+
+<p>On the receiving side, the fact that Unjellying insists upon a
+user-registered class for each potential incoming instance reduces the risk
+that arbitrary code will be executed on behalf of remote clients. Only the
+classes that are added to the <code>unjellyableRegistry</code> need to be
+examined. Half of the security issues in RPC systems will boil down to the
+fact that these potential unserializing classes will have their
+<code>setCopyableState</code> methods called with a potentially hostile
+<code>state</code> argument. (the other half are that <code>remote_</code>
+methods can be called with arbitrary arguments, including instances that
+have been sent to that client at some point since the current connection was
+established). If the system is prepared to handle that, it should be in good
+shape security-wise.</p>
+
+<p>RPC systems which allow remote clients to create arbitrary objects in the
+local namespace are liable to be abused. Code gets run when objects are
+created, and generally the more interesting and useful the object, the more
+powerful the code that gets run during its creation. Such systems also have
+more assumptions that must be validated: code that expects to be given an
+object of class <code>A</code> so it can call <code>A.foo</code> could be
+given an object of class <code>B</code> instead, for which the
+<code>foo</code> method might do something drastically different. Validating
+the object is of the required type is much easier when the number of
+potential types is smaller.</p>
+
+<h3>Controlled Method Invocation</h3>
+
+<p>Objects which allow remote method invocation do not provide remote access
+to their attributes (<code>pb.Referenceable</code> and
+<code>pb.Copyable</code> are mutually exclusive). Remote users can only
+invoke a well-defined and clearly-marked subset of their methods: those with
+names that start with <code>remote_</code> (or other specific prefixes
+depending upon the variant of <code>Referenceable</code> in use). This
+insures that they can have local methods which cannot be invoked remotely.
+Complete object transparency would make this very difficult: the
+<q>translucent</q> reference scheme allows objects some measure of privacy
+which can be used to implement a security model. The
+<q><code>remote_</code></q> prefix makes all remotely-invokable methods easy
+to locate, improving the focus of a security audit.</p>
+
+<h3>Restricted Object Access</h3>
+
+<p>Objects sent by reference are indexed by a per-connection ID number,
+which is the only way for the remote end to refer back to that same object.
+This list means that the remote end can not touch objects that were not
+explicitly given to them, nor can they send back references to objects
+outside that list. This protects the program's memory space against the
+remote end: they cannot find other local objects to play with.</p>
+
+<p>This philosophy of using simple, easy to validate identifiers (integers
+in the case of PB) that are scoped to a well-defined trust boundary (in this
+case the Broker and the one remote system it is connected to) leads to
+better security. Imagine a C system which sent pointers to the remote end
+and hoped it would receive back valid ones, and the kind of damage a
+malicious client could do. PB's <code>.localObjects{}</code> table insures
+that any given client can only refer to things that were given to them. It
+isn't even a question of validating the identifier they send: if it isn't a
+value of the <code>.localObjects{}</code> dictionary, they have no physical
+way to get at it. The worst they can do with a corrupt ObjectID is to cause
+a <code>KeyError</code> when it is not found, which will be trapped and
+reported back.</p>
+
+<h3>Size Limits</h3>
+
+<p>Banana limits string objects to 640k (because, as the source says, 640k
+is all you'll ever need). There is a helper class called
+<code>pb.util.StringPager</code> that uses a producer/consumer interface to
+break up the string into separate pages and send them one piece at a time.
+This also serves to reduce memory consumption: rather than serializing the
+entire string and holding it in RAM while waiting for the transmit buffers
+to drain, the pages are only serialized as there is space for them.</p>
+
+
+<h2>Future Directions</h2>
+
+<p>PB can currently be carried over TCP and SSL connections, and through
+UNIX-domain sockets. It is being extended to run over UDP datagrams and a
+work-in-progress reliable datagram protocol called <q>airhook</q>. (clearly
+this requires changes to the authorization sequence, as it must all be done
+in a single packet: it might require some kind of public-key signature).</p>
+
+<p>At present, two functions are used to obtain the initial reference to a
+remote object: <code>pb.getObjectAt</code> and <code>pb.connect</code>. They
+take a variety of parameters to indicate where the remote process is
+listening, what kind of username/password should be used, and which exact
+object should be retrieved. This will be simplified into a <q>PB URL</q>
+syntax, making it possible to identify a remote object with a descriptive
+URL instead of a list of parameters.</p>
+
+<p>Another research direction is to implement <q>typed arguments</q>: a way
+to annotate the method signature to indicate that certain arguments may only
+be instances of a certain class. Reminiscent of the E language, this would
+help remote methods improve their security, as the common code could take
+care of class verification.</p>
+
+<p>Twisted provides a <q>componentization</q> mechanism to allow
+functionality to be split among multiple classes. A class can declare that
+all methods in a given list (the <q>interface</q>) are actually implemented
+by a companion class. Perspective Broker will be cleaned up to use this
+mechanism, making it easier to swap out parts of the protocol with different
+implementations.</p>
+
+<p>Finally, a comprehensive security audit and some performance improvements
+to the Jelly design are also in the works.</p>
+
+<!-- $Id: pb.html,v 1.1 2003/03/31 05:21:40 glyph Exp $ -->
+
+</body> </html>
diff --git a/doc/historic/2003/pycon/releasing/releasing-twisted b/doc/historic/2003/pycon/releasing/releasing-twisted
new file mode 100755
index 0000000..1b20155
--- /dev/null
+++ b/doc/historic/2003/pycon/releasing/releasing-twisted
@@ -0,0 +1,151 @@
+#!/usr/bin/python2.2
+# Moshe -- This seems like 30+ minutes to me!
+from slides import NumSlide, Slide, Bullet, SubBullet, PRE, URL
+from twslides import Lecture
+
+
+lecture = Lecture(
+ "Managing the Release of a Large Python Project",
+ Slide("About Twisted",
+ Bullet("Networking framework"),
+ Bullet("Other goodies"),
+ Bullet("60,000 lines of code"),
+ Bullet("Things can (and do) go wrong"),
+ ),
+ Slide("Python",
+ Bullet("Recap"),
+ Bullet("No compilation (except for native modules)"),
+ Bullet("Simple file-based modules (no registration)"),
+ Bullet("Distutils -- Does the common things"),
+ ),
+ Slide("Release Procedure -- Steps",
+ Bullet("Increment version in copyright file, README"),
+ Bullet("Tag release"),
+ Bullet("Export from CVS"),
+ Bullet("Rename toplevel directory"),
+ Bullet("Generate API and HOWTO documentation"),
+ Bullet("Create tarballs"),
+ Bullet("Move tarballs to target area"),
+ Bullet("Create Debian packages"),
+ Bullet("Put Debian packages in final place"),
+ Bullet("Upgrade production machine"),
+ ),
+ Slide("Release Procedure Overview - Documentation",
+ Bullet("Man pages -> Lore"),
+ Bullet("Lore documents -> HTML"),
+ Bullet("Lore documents -> PS/PDF"),
+ Bullet("API documentation -> HTML"),
+ ),
+ Slide("Release Procedure Overview - Testing",
+ Bullet("Run of the mill unit tests"),
+ Bullet("Acceptance tests of less portable things"),
+ Bullet("Prerelease tests for twistedmatrix.com-specific test"),
+ Bullet("twistedmatrix.com uses latest version -- always!"),
+ ),
+ Slide("Release Procedure Overview - Debian",
+ Bullet("The Twisted machines use Debian packages"),
+ Bullet("The Twisted machines run latest version"),
+ Bullet("Debian packages are built as part of the release procedure"),
+ ),
+ Slide("Overview Summary",
+ Bullet("Many steps"),
+ Bullet("Each can fail", SubBullet(
+ Bullet("Documentation can fail to build"),
+ Bullet("Tests can fail"),
+ Bullet("Debian packages can fail to build")),
+ ),
+ Bullet("Need robust automated setup"),
+ ),
+ Slide("Enter Release-Twisted",
+ Bullet("Python program to release Twisted"),
+ Bullet("Key word -- Robust"),
+ Bullet("Based on actions which can undo"),
+ Bullet("Flexible - able to recover a botched build from the middle"),
+ Bullet("Easy - has good defaults"),
+ ),
+ Slide("Testing - Recap",
+ Bullet("Testing is special - no effect"),
+ Bullet("The more, the better"),
+ Bullet("Harder to automate - machines can't tell right from wrong",
+ SubBullet(Bullet("Except in Hollywood")),
+ ),
+ ),
+ Slide("Different Kinds of Tests - Unit Tests",
+ Bullet("Completely automated"),
+ Bullet("Completely machine-verifiable"),
+ Bullet("Portable"),
+ Bullet("Must always pass"),
+ ),
+ Slide("Different Kinds of Tests - Acceptance Tests",
+ Bullet("Interacts with user"),
+ Bullet("Probably works only on Linux"),
+ Bullet("Assumes many client side tools"),
+ Bullet("Exercises many parts of Twisted which are hard in unit tests"),
+ ),
+ Slide("Acceptance Tests Examples",
+ Bullet("Run Twisted web server, run user-defined web browser"),
+ Bullet("Run mail server, send mail and try to download with pop3"),
+ Bullet("Run IRC server, run user-defined IRC client"),
+ ),
+ Slide("Different Kinds of Tests - Prerelease Tests",
+ Bullet("TwistedMatrix.com dogfoods"),
+ Bullet("We want to test the dog food"),
+ Bullet("prerelease tests convince us that this version doesn't break "
+ "completely"),
+ Bullet("Among other things, tests that distributed web works"),
+ ),
+ Slide("Epydoc",
+ ),
+ Slide("Epyrun",
+ ),
+ Slide("Distutils -- Datafiles",
+ ),
+ Slide("Distutils -- Conditional compilation",
+ ),
+ Slide("Distutils -- Conditional compilation woes",
+ ),
+ Slide("Distutils -- Other woes",
+ Bullet("Versions -- keywords were added later"),
+ Bullet("Icky to do platform dependent stuff"),
+ ),
+ Slide("release-twistd -- master script",
+ ),
+ Slide("Commit/rollback",
+ ),
+ Slide("CVS and tagging",
+ ),
+ Slide("Debian Packages -- Challenges",
+ Bullet("Versioning: We want 1.0.2alpha4 to precede 1.0.2"),
+ Bullet("Dependencies: Which versions of Python? 2.1? 2.2? 2.3?"),
+ Bullet("Dependencies: Which libc version?"),
+ ),
+ Slide("Debian Packages -- Solutions",
+ Bullet("Build two sets -- for Debian stable and for Debian unstable"),
+ Bullet("When building on stable, remove python2.3-dev from build"
+ " dependencies", SubBullet(
+ Bullet("This stops the Python 2.3 version from being built")),
+ ),
+ Bullet("If building a non-final version, name it 1.0.1+1.0.2alpha4"),
+ Bullet("Unstable build is done by sshing into an unstable chroot"),
+ ),
+ Slide("Windows Releases -- Challenges",
+ ),
+ Slide("Windows Releases -- Solutions",
+ ),
+ Slide("Why Not Dependency Management?",
+ ),
+ Slide("Conclusions",
+ Bullet("Distutils does not do enough"),
+ Bullet("Cross compiling is hard"),
+ Bullet("It would be nice if Python had integrated docstring tools"),
+ Bullet("Wheel reinvention is useful"),
+ ),
+ Slide("Future Directions",
+ Bullet("RPMs for Various Distributions"),
+ Bullet("More automation"),
+ ),
+ Slide("Questions?",
+ ),
+)
+
+lecture.renderHTML(".", "releasing-%d.html", css="main.css")
diff --git a/doc/historic/2003/pycon/releasing/releasing.html b/doc/historic/2003/pycon/releasing/releasing.html
new file mode 100644
index 0000000..9e7bb61
--- /dev/null
+++ b/doc/historic/2003/pycon/releasing/releasing.html
@@ -0,0 +1,491 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+<title>Managing the Release of a Large Python Project</title>
+</head>
+
+<body>
+<h1>Managing the Release of a Large Python Project</h1>
+
+<ul>
+<li>Christopher Armstrong <a href="mailto:radix@twistedmatrix.com">radix@twistedmatrix.com</a></li>
+<li>Moshe Zadka <a href="mailto:moshez@twistedmatrix.com">moshez@twistedmatrix.com</a></li>
+</ul>
+
+<h2>Abstract</h2>
+<p>
+
+Twisted is a Python networking framework. At last count, the project
+contains nearly 60,000 lines of effective code (not comments or blank
+lines). When preparing a release, many details must be checked, and
+many steps must be followed. We describe here the technologies and
+tools we use, and explain how we built tools on top of them which help
+us make releasing as painless as possible.
+
+</p>
+
+<h2>Introduction</h2>
+<p>
+
+One of the virtues of Python is the ease of distributing code. Its
+module system and the lack of necessity of compilation are what make
+this possible. This means that for simple Python projects, nothing
+more complicated then tar is needed to prepare a distribution of a
+library. However, Twisted has auto-generated documentation in several
+formats, including docstring generated documentation, HOWTOs written
+in HTML, and manpages written in nroff. As Twisted grew more complex
+and popular, a detailed procedure for putting out a release was made
+necessary. However, human fallibility being what it is, it was decided
+that most of these steps should be automated.
+
+</p>
+
+<h2>Overview of Steps</h2>
+<p>
+
+Despite heavy automation, there are still a number of manual steps
+involved in the release process. We've reduced the amount of manual
+steps quite a bit, and most of what's left is not fully automatable,
+although the process could be made easier (see <q>Future
+Directions</q> below).
+
+</p>
+
+<ul>
+ <li>Test
+ <ul>
+ <li>Unit tests</li>
+ <li>Acceptance tests</li>
+ <li>Pre-release tests</li>
+ </ul>
+ </li>
+ <li>Update the Changelog and README files</li>
+ <li>Run the release script
+ <ul>
+ <li>unix runs admin/release-twisted</li>
+ <li>Win32 runs win32/bdist_wininst.bat</li>
+ </ul>
+ </li>
+ <li>Deploy: update twisted deployment on twistedmatrix.com</li>
+ <li>Upload to SourceForge mirror</li>
+ <li>Update Website</li>
+</ul>
+
+
+
+<h2>Testing</h2>
+
+<p>
+
+Twisted has three categories of tests: unit, acceptance, and
+pre-release. Testing is an important part of releasing quality
+software, of course, so these will be explained.
+
+</p>
+
+
+<p>
+
+Unit tests are run as often as possible by each of the developers as
+they write code, and must pass before they commit any changes to
+CVS. While the Twisted team tries to follow the XP practice of
+ensuring all code is releasable, this isn't always true. Thus, running
+the unit tests on several platforms before releasing is necessary.
+Our BuildBot runs the unit tests constantly on several hosts and
+multiple platforms, so the <a
+href="http://twistedmatrix.com/users/warner.twistd/">status page</a>
+is simply checked for green lights before a release.
+
+</p>
+
+<p>
+
+Acceptance tests (which, unfortunately, are not quite the same as <a
+href="http://xprogramming.org/">Extreme Programming's</a> Acceptance
+Tests) are simply interactive tests of various Twisted services. There
+is a script that executes several system commands that use the Twisted
+end-user executables and start several clients (web browsers, IRC
+clients, etc) to allow the user to interactively test the different
+services that Twisted offers. These are only routinely run before a
+release, but we also encourage developers to run these before they
+make major changes.
+
+</p>
+
+<p>
+
+The pre-release tests are for ensuring the web server (One of the most
+popular parts of Twisted, and which the twistedmatrix.com web site
+uses) runs correctly in a semi-production environment. The script
+starts up a web server on twistedmatrix.com, similar to the one on
+port 80, but on an out-of-the-way port. <q>lynx</q> is then run
+several times, with URLs strategically chosen to test different
+features of the web server. Afterwards, the log of the web server is
+displayed and the user is to check for any errors.
+
+</p>
+
+
+<h2>The release-twisted Script</h2>
+
+<p>
+
+Like many other build/release systems, the automated parts of our
+release system started out as a number of small shell
+scripts. Eventually these became a single Python script which was a
+large improvement, but still had many problems, especially since our
+release process became more complex (documentation generation,
+different types of archive formats, etc). This led to problems with
+steps in the middle of the process breaking; the release manager would
+need to restart the entire thing, or enter the remaining commands
+manually.
+
+</p>
+
+<p>
+
+The solution that we came up with was a simple framework for
+pseudo-transactions; Every step of the process is implemented with a
+class that has <code class="python">doIt</code> and <code
+class="python">undoIt</code> methods. Each step also has a
+command-line argument associated with it, so a typical run of the
+script looks something like this:
+
+<pre class="shell">
+$SOMEWHERE/admin/release-twisted -V $VERSION -o $LASTVERSION --checkout \
+--release=/twisted/Releases --upver --tag --exp --dist --docs --balls \
+--rel --deb --debi
+</pre>
+
+</p>
+
+<h3>Transactions</h3>
+
+<p>
+
+As stated above, our transaction system is very simple. One of our
+rather simple transaction classes is <code
+class="python">Export</code>.
+
+</p>
+
+
+<pre class="python">
+class Export(Transaction):
+ def doIt(self, opts):
+ print "Export"
+ root = opts['cvsroot']
+ ver = opts['release-version']
+ sh('cvs -d%s export -r release-%s Twisted' % (root, ver.replace('.', '_')))
+
+ def undoIt(self, opts, fail):
+ sh('rm -rf Twisted')
+</pre>
+
+
+<p>
+
+One useful feature to note is the <code
+class="python">sensitiveUndo</code> attribute on Transaction
+classes. If a transaction has this set, the user will be prompted
+before running the <code class="python">undoIt</code> method. This is
+useful for very long-running processes, like documentation generation,
+debian package building, and uploading to sourceforge. If something
+goes wrong in the middle of one of these processes, we want to give
+the user a chance to manually fix the problem rather than redoing the
+entire transaction. They can then continue from the next command by
+omitting the commands that have already been accomplished from the
+<code class="shell">release-twisted</code> arguments.
+
+</p>
+
+<p>
+
+A list of all of the transactions defined in release-twisted follows.
+
+</p>
+
+<dl>
+<dt>CheckOut</dt>
+<dd>
+
+ checks out the latest revision of Twisted from CVS and puts it in
+ the <q>Twisted CVS</q> directory.
+
+</dd>
+
+<dt>UpdateVersion</dt>
+<dd>
+
+ changes the version number of the current release -- updating
+ twisted/copyright.py (the canonical location for the current
+ version) and a few other text files where the current version is
+ mentioned.
+
+</dd>
+
+
+<dt>Tag</dt>
+<dd>
+
+ tags the revisions in the current source tree with the version
+ passed in on the command line.
+
+</dd>
+
+
+<dt>Export</dt>
+
+<dd>
+
+ runs the cvs <q>export</q> command, which is similar to
+ <q>checkout</q>, but leaves out CVS support directories; this is
+ what we package up in the archives.
+
+</dd>
+
+
+<dt>PrepareDist</dt>
+<dd>
+
+ simply copies the directory containing the version of Twisted to be
+ released to a new directory specifically for the release
+ process. The reason that we have this extra copy is that sometimes
+ one will want to create a release from a directory that wasn't
+ created from the <q>Export</q> command; having the release script
+ munge that directory in-place would be impolite.
+
+</dd>
+
+
+<dt>GenerateDocs</dt>
+
+<dd>
+
+ generates the various documentation: HTML API documentation (via
+ Epydoc), HTML, PostScript, and PDF howto documentation (via
+ twisted.lore), and HTML man-pages (via lore, converted from the
+ nroff source).
+
+</dd>
+
+<dt>CreateTarballs</dt>
+<dd>
+
+ creates the various archives that each Twisted release involves:
+ tarred and gzipped or bzip2ed versions of archives with code plus
+ documentation, code without documentation, and only documentation.
+
+</dd>
+
+
+<dt>Release</dt>
+
+<dd>
+
+ copies all of the archives to a directory specified by the --release
+ parameter. This is meant to be a publically accessible directory,
+ thus the name <q>Release</q>.
+
+</dd>
+
+<dt>MakeDebs</dt>
+
+<dd>
+
+ creates the .deb packages and support files for the Twisted Debian
+ packages.
+
+</dd>
+
+<dt>InstallDebs</dt>
+
+<dd>
+
+ Creates an apt-gettable Debian package repository in the
+ (unfortunately hard-coded) <q>/twisted/Debian</q> directory.
+
+</dd>
+
+<dt>Sourceforge</dt>
+
+<dd>
+
+ uploads the archives and debian packages to Twisted's sourceforge
+ mirror at <a
+ href="http://twisted.sourceforge.net">http://twisted.sourceforge.net/</a>.
+
+</dd>
+
+
+<dt>UpgradeDebian</dt>
+
+<dd>
+
+ Installs the recently-generated Debian packages via <q>dpkg</q> on
+ the local machine.
+
+</dd>
+
+</dl>
+
+
+<h2>setup.py</h2>
+
+<p>
+
+Twisted has an extensive and very customized setup.py script. We have
+a number of C extension modules and try to ensure that they all build,
+or at least fail gracefully, on win32, Mac OSX, Linux and other
+popular unix-style OSes.
+
+</p>
+
+<p>
+
+We have overridden three of the distutils <q>command classes</q>:
+<code class="python">build_ext</code>, <code
+class="python">install_scripts</code>, and <code
+class="python">install_data</code>.
+
+</p>
+
+
+<h3>Building C extensions</h3>
+
+<p>
+
+<code class="python">build_ext_twisted</code> detects, based on
+various features of the platform, which C extensions to build. It
+overrides the <code class="python">build_extensions</code> method to
+first check which C extensions are appropriate to build for the
+current platform before proceeding as normal (by calling the
+superclass's <code class="python">build_extensions</code>). The
+module-detection consists of several simple tests for platform
+features and conditional additions to the `extensions' attribute. One
+especially useful feature is the <code
+class="python">_check_header</code> method, which takes the name of an
+arbitrary head file and tries to compile (via the distutil's C
+compiler interafce) a simple C file that only #includes it.
+
+</p>
+
+
+<h3>Installing scripts</h3>
+
+
+<p>
+
+<code class="python">install_data_twisted</code> ensures that the data
+files are installed along-side the python modules in the twisted
+package. This is accomplished with the incantation:
+
+</p>
+
+<pre class="python">
+class install_data_twisted(install_data):
+ def finalize_options (self):
+ self.set_undefined_options('install',
+ ('install_lib', 'install_dir')
+ )
+ install_data.finalize_options(self)
+</pre>
+
+
+
+<h3>Windows Releases</h3>
+
+<!--
+<p>
+This section will cover the problems with packaging Python projects
+for windows, especially ones which contain scripts. The problem of
+clickability is especially acute, as windows determines types by
+extensions and not by #! lines.
+</p>
+-->
+
+<p>
+
+Packaging software for windows involves a unique set of problems. The
+problem of clickability is especially acute; Several customizations to
+the distutils setup had to be made.
+
+</p>
+
+<p>
+
+The first customization was to make the <q>scripts</q> end with a
+<q>.py</q> extension, since Windows relies on extension rather than a
+she-bang line to specify what interpreter should execute a file. This
+was accomplished by overriding the <code
+class="python">install_scripts</code> command, like so:
+
+</p>
+
+<pre class="python">
+class install_scripts_twisted(install_scripts):
+ """Renames scripts so they end with '.py' on Windows."""
+
+ def run(self):
+ install_scripts.run(self)
+ if os.name == "nt":
+ for file in self.get_outputs():
+ if not file.endswith(".py"):
+ os.rename(file, file + ".py")
+</pre>
+
+
+<p>
+
+We also wanted to have a Start-menu group with a number of icons for
+running different Twisted programs. This was accomplished with a
+post-install script specified with the command-line parameter
+<code class="shell">--install-script=twisted_postinstall.py</code>.
+
+</p>
+
+
+
+<h2>Future Directions</h2>
+
+<p>
+
+The theme is, of course, automation, and there are still many manual
+steps involved in a Twisted release. The currently most annoying step
+is updating the documentation and downloads section of the
+twistedmatrix.com website. Automating this would be a major
+improvement to the time it takes from the running of the release
+script to a fully completed release.
+
+</p>
+
+<p>
+
+Another major improvement will involve further integration with
+BuildBot. Currently we have BuildBot running unit tests, building C
+extensions, and generating documentation on several hosts. Eventually
+we would like to have it constantly generating full release archives,
+and have an additional web form for <q>finalizing</q> any particular
+build that we deem releasable. The result would be uploading the
+release to the mirrors and updating the website.
+
+</p>
+
+<p>
+
+The tagging scheme used by the release-twisted scripts can sometimes
+be problematic. If we find serious problems in the code-base after the
+Tag command is executed (which is fairly early in the process), we are
+forced to fix the bug and increase the version number. This can be
+prevented by, instead of making the official tag, using the unofficial
+tag <q>releasing-$version</q> (as opposed to <q>release-$version</q>)
+at that early stage. Once most of the steps are complete, the official
+tag will be made. If something in between goes wrong, we can just
+re-use the unofficial <q>releasing-$version</q> tag and not worry
+about users trying to use that tag.
+
+</p>
+
+
+</body>
+</html>
diff --git a/doc/historic/2003/pycon/tw-deploy/tw-deploy b/doc/historic/2003/pycon/tw-deploy/tw-deploy
new file mode 100755
index 0000000..294bd73
--- /dev/null
+++ b/doc/historic/2003/pycon/tw-deploy/tw-deploy
@@ -0,0 +1,184 @@
+#!/usr/bin/python
+# Requires CVS Slides
+
+from slides import Lecture, Slide, TitleSlide, Image, Bullet, PRE, URL, SubBullet, NumSlide, toHTML
+
+PERL_PROCESSOR = """\
+from twisted.web import static, twcgi
+
+class PerlScript(twcgi.FilteredScript):
+ filter = '/usr/bin/perl' # Points to the perl parser
+"""
+
+RPY_EXAMPLE = """\
+from twisted.web import resource
+
+class MyGreatResource(resource.Resource):
+ def render(self, request):
+ return "<html>foo</html>"
+
+resource = MyGreatResource()
+"""
+
+lecture = Lecture(
+ "A Twisted Web Tutorial",
+
+ TitleSlide("Twisted Web -- A tutorial",
+ Image("twistedlogo.png"),
+ ),
+
+ Slide("Twisted Web -- Where does it fit?",
+ Image("twisted-overview.png"),
+ ),
+
+ Slide("Setup and Configuration Utilities",
+ Bullet("mktap"),
+ Bullet("twistd"),
+ Bullet("websetroot"),
+ ),
+
+ Slide("mktap",
+ Bullet("TAP Model"),
+ Bullet("General usage"),
+ Bullet("Flexibility and Power"),
+ ),
+
+ Slide("mktap web : Common Useful Options",
+ Bullet("--path"),
+ Bullet("--port"),
+ Bullet("--user"),
+ Bullet("--logfile"),
+ Bullet("--processor"),
+ ),
+
+ Slide("Sample mktap command lines",
+ Bullet(PRE("mktap web")),
+ Bullet(PRE("mktap web --path=/var/www --logfile=/var/log/twistedweb.log")),
+ Bullet(PRE("mktap web --port=80 --path=/var/www --user --mime-type=text/plain")),
+ Bullet(PRE("mktap web --path=/home/nafai/public_html --processor=.pl=PerlProcessor.PerlProcessor --index=index.pl")),
+ ),
+
+ Slide("twistd : An overview",
+ Bullet("Start a Twisted Application"),
+ Bullet("Loads and instance of twisted.internet.app.Application from a file"),
+ Bullet("Daemonizes, binds to appropriate ports, and starts the Twisted mainloop"),
+ ),
+
+ Slide("Sample twistd command lines",
+ Bullet(PRE("twistd -f web.tap -l /var/log/twisted.log")),
+ Bullet(PRE("twistd -f web.tap --pidfile /var/run/web.pid")),
+ ),
+
+ Slide("Shutting down twistd",
+ Bullet("On Unix (in general): "),
+ SubBullet(
+ Bullet(PRE("kill -9 `cat twistd.pid`"))),
+ Bullet("On Windows: "),
+ SubBullet(
+ Bullet("Cannot daemonize on Windows, so just run twistd in a command prompt"),
+ Bullet("Switch to the command prompt, and press Control-C"),
+ ),
+ ),
+
+ Slide("Shutdown TAPs",
+ Bullet("Since TAPs store persistent data for an application,\
+ a 'shutdown' TAP is created on twistd shutdown"),
+ Bullet("You'll often want to start your Twisted application\
+ on subsequent runs with the shutdown TAP"),
+ ),
+
+ Slide("twistd and security",
+ Bullet("When twistd is run as root, it will shed root privileges\
+ for the uid and gid of either the user that created the TAP or those\
+ specified on the mktap commandline."),
+ ),
+
+ # Try this out!
+ Slide("websetroot",
+ Bullet("Used to change what the root of the server points to"),
+ Bullet("Set it to a Resource contained either in a Python source file or a Pickle file"),
+ ),
+
+ Slide("Sample websetroot command lines",
+ Bullet(PRE("websetroot -p 80 -f web.tap --script rootResource.py")),
+ Bullet(PRE("websetroot -p 8080 -f web.tap --pickle rootPickle")),
+ ),
+
+ # Resource example
+ # Use the perl example from the docs
+ Slide("What's a Resource?",
+ Bullet("Everything in twisted in represented as twisted.web.resource.Resource object"),
+ Bullet("In general, two calls are made on a resource:"),
+ SubBullet(
+ Bullet(PRE("getChild()")),
+ Bullet(PRE("render()")),
+ ),
+ ),
+
+ Slide("Resource call examples:",
+ Bullet("/foo/bar/baz gets converted to:"
+ ),
+ SubBullet(
+ Bullet(PRE("site.getChild('foo', request).getChild('bar', request).getChild('baz', request).render(request)")),
+ ),
+ Bullet("/foo/bar/baz/ gets converted to:"
+ ),
+ SubBullet(
+ Bullet(PRE("site.getChild('foo', request).getChild('bar', request).getChild('baz', request).getChild('', request).render(request)")),
+ ),
+ ),
+
+ Slide("What do Resources handle?",
+ Bullet("Out of the box, Twisted supports files of all types"),
+ Bullet("HTML, text, etc."),
+ Bullet("Default MIME type can be specified"),
+ ),
+
+ Slide("What about web development?",
+ Bullet("Twisted Web has what are called processors, which are instances\
+ of classes inherited from resource.Resource"),
+ Bullet("By default, Twisted supports the following file types:"),
+ SubBullet(
+ Bullet(".php"),
+ Bullet(".php3"),
+ Bullet(".cgi"),
+ Bullet(".epy"),
+ Bullet(".rpy"),
+ Bullet(".trp"),
+ ),
+ Bullet("You can also write your own"),
+ ),
+
+ Slide("Custom Processor: More than One Evil Way to Do It",
+ Bullet("A custom processor to handle Perl CGIs:"),
+ PRE(PERL_PROCESSOR),
+ Bullet("An example of how to use:"),
+ SubBullet(Bullet(PRE("mktap web --path=/home/nafai/public_html --processor=.pl=PerlScript.PerlScript")),
+ ),
+ ),
+
+ Slide("What about making my own resources?",
+ Bullet("Define a class that inherits from resource.Resource"),
+ Bullet("Define the render() method on that class"),
+ Bullet("For long requests, render() can return NOT_DONE_YET"),
+ Bullet("Then Create a .rpy file that sets resource = to an instance of the class"),
+ ),
+
+ Slide(".rpy example",
+ PRE(RPY_EXAMPLE),
+ ),
+
+ Slide("More Stuff",
+ Bullet("In other words, the slides I didn't get to write..."),
+ SubBullet(
+ Bullet("Distributed Servers"),
+ Bullet("Virtual Hosts"),
+ Bullet("Rewrite Rules"),
+ Bullet("Debian configuration"),
+ Bullet("twistedmatrix.com configuration"),
+ ),
+ ),
+)
+
+if __name__ == '__main__':
+ lecture.renderHTML(".", "tw_deploy-%02d.html", css="main.css")
diff --git a/doc/historic/2003/pycon/tw-deploy/twisted-overview.png b/doc/historic/2003/pycon/tw-deploy/twisted-overview.png
new file mode 100644
index 0000000..6746a85
--- /dev/null
+++ b/doc/historic/2003/pycon/tw-deploy/twisted-overview.png
Binary files differ
diff --git a/doc/historic/2003/pycon/tw-deploy/twistedlogo.png b/doc/historic/2003/pycon/tw-deploy/twistedlogo.png
new file mode 100644
index 0000000..6226297
--- /dev/null
+++ b/doc/historic/2003/pycon/tw-deploy/twistedlogo.png
Binary files differ
diff --git a/doc/historic/2003/pycon/twisted-internet/twisted-internet.py b/doc/historic/2003/pycon/twisted-internet/twisted-internet.py
new file mode 100644
index 0000000..1948d3e
--- /dev/null
+++ b/doc/historic/2003/pycon/twisted-internet/twisted-internet.py
@@ -0,0 +1,541 @@
+#!/usr/bin/python
+
+from slides import Lecture, Slide, Image, Bullet, PRE, URL, SubBullet, NumSlide, toHTML
+import os
+
+class Bad:
+ """Marks the text in red."""
+
+ def __init__(self, text):
+ self.text = text
+
+ def toHTML(self):
+ return '<font color="red">%s</font>' % toHTML(self.text)
+
+
+class Lecture(Lecture):
+
+ def getFooter(self):
+ return '<div class="footer"><hr noshade />Presented by <b>ZOTECA&nbsp;</b></div>'
+
+
+EVENT_LOOP_CODE = """\
+# pseudo-code reactor
+class Reactor:
+ def run(self):
+ while 1:
+ e = self.getNextEvent()
+ e.run()
+"""
+
+PROTOCOL_CODE = """\
+from twisted.internet.protocol import Protocol
+
+class Echo(Protocol):
+ def connectionMade(self):
+ print 'connection made with', self.transport.getPeer()
+ def dataReceived(self, data):
+ self.transport.write(data)
+ def connectionLost(self, reason):
+ print 'connection was lost, alas'
+"""
+
+SERVER_CODE = """\
+from twisted.internet.protocol import ServerFactory
+
+class EchoFactory(ServerFactory):
+
+ def buildProtocol(self, addr):
+ p = Echo()
+ p.factory = self
+ return p
+"""
+
+RUNNING_SERVER_CODE = """\
+from twisted.internet import reactor
+
+f = EchoFactory()
+reactor.listenTCP(7771, f)
+reactor.run()
+"""
+
+CLIENT_PROTOCOL_CODE = """\
+from twisted.internet.protocol import Protocol
+
+class MyClientProtocol(Protocol):
+ buffer = ''
+ def connectionMade(self):
+ self.transport.write('hello world')
+ def dataReceived(self, data):
+ self.buffer += data
+ if self.buffer == 'hello world':
+ self.transport.loseConnection()
+"""
+
+CLIENT_FACTORY_CODE = """\
+from twisted.internet.protocol import ClientFactory
+
+class MyFactory(ClientFactory):
+
+ protocol = MyClientProtocol
+
+ def startedConnecting(self, connector):
+ pass # we could connector.stopConnecting()
+ def clientConnectionMade(self, connector):
+ pass # we could connector.stopConnecting()
+ def clientConnectionLost(self, connector, reason):
+ connector.connect() # reconnect
+ def clientConnectionFailed(self, connector, reason):
+ print "connection failed"
+ reactor.stop()
+"""
+
+CLIENT_CONNECT_CODE = """\
+from twisted.internet import reactor
+
+reactor.connectTCP('localhost', 7771, MyFactory(), timeout=30)
+reactor.run()
+"""
+
+PULL_PRODUCER_CODE = """\
+class FileProducer:
+
+ def __init__(self, file, size, transport):
+ self.file = file; self.size = size
+ self.transport = transport # the consumer
+ transport.registerProducer(self, 0)
+
+ def resumeProducing(self):
+ if not self.transport: return
+ self.transport.write(self.file.read(16384))
+ if self.file.tell() == self.size:
+ self.transport.unregisterProducer()
+ self.transport = None
+
+ def pauseProducing(self): pass
+
+ def stopProducing(self):
+ self.file.close()
+ self.request = None
+"""
+
+PUSH_PRODUCER_CODE = """\
+from twisted.internet import reactor
+
+class GarbageProducer:
+
+ def __init__(self, transport):
+ self.paused = 0; self.stopped = 0
+ self.transport = transport
+ transport.registerProducer(self, 1)
+ self.produce()
+
+ def produce(self):
+ if not self.paused:
+ self.transport.write('blabla')
+ if not self.stopped:
+ reactor.callLater(0.1, self.produce)
+
+ def stopProducing(self):
+ self.stopped = 1
+
+ def pauseProducing(self):
+ self.paused = 1
+
+ def resumeProducing(self):
+ self.paused = 0
+"""
+
+SCHEDULING_CODE = """\
+from twisted.internet import reactor
+
+def f(x, y=1):
+ print x, y
+
+i = reactor.callLater(0.1, f, 2, y=4)
+i.delay(2)
+i.reset(1)
+i.cancel()
+"""
+
+FACTORY_START_CODE = """\
+from twisted.internet.protocol import ServerFactory
+
+class LogFactory(ServerFactory):
+
+ def startFactory(self):
+ self.log = open('log.txt', 'w')
+
+ def stopFactory(self):
+ self.log.close()
+"""
+
+LOGGING_CODE = """\
+from twisted.python import log
+
+# by default only errors are logged, to stderr
+logFile = open('log.txt', 'a')
+log.startLogging(logFile)
+
+log.msg('Something has occurred')
+"""
+
+LOGGING_ERRORS_CODE = """
+from twisted.python import log, failure
+
+e = ValueError('ONO')
+log.err(failure.Failure(e))
+
+try:
+ doSomethingElse()
+except:
+ log.deferr()
+"""
+
+SERVICE_CODE = """\
+from twisted.internet import app
+
+class FooService(app.ApplicationService):
+ def startService(self):
+ # do startup stuff
+ def stopService(self):
+ # do shutdown stuff
+ def foobrizate(self):
+ # business logic!
+
+application = app.Application('foobnator')
+svc = FooService('foo', application)
+application.getServiceNamed('foo') is svc # True
+"""
+
+RUNNABLE_APP_CODE = """\
+# this is web.py
+from twisted.internet import app
+from twisted.web import static, server
+
+application = app.Application('web')
+application.listenTCP(8080, server.Site(static.File('/var/www')))
+
+if __name__ == '__main__':
+ application.run(save=0)
+"""
+
+TWISTD_CODE = """\
+$ twistd -y web.py
+$ lynx http://localhost:8080
+$ kill `cat twistd.pid`
+"""
+
+GUI_CODE = """\
+from twisted.internet import gtkreactor
+gtkreactor.install()
+import gtk
+w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+w.show_all()
+from twisted.internet import reactor
+reactor.run()
+"""
+
+lecture = Lecture(
+ "The twisted.internet Tutorial of Doom",
+
+ Slide("Part 1 - Introduction"),
+
+ # there are different ways to do networking
+ # mention processes are not cross-platform
+ Slide("Choosing a networking paradigm for the enterprise",
+ Bullet("Event driven"),
+ Bullet(Bad("Threads")),
+ Bullet("Others which we will ignore (processes, SEDA, ...)")),
+
+ # it's a metaphor!
+ Slide("Applied Bistromathics 101",
+ Bullet("Consider a restaurant as a network application"),
+ Bullet("Clients come in, make requests to the waiters"),
+ Bullet("Waiters act on clients' choices")),
+
+ # an event loop is efficient, doesn't waste time
+ # event loop is also used for GUIs
+ Slide("The event driven waiter",
+ Bullet("One waiter, serving all tables"),
+ Bullet("Waiter takes orders from tables to kitchen"),
+ Bullet("Waiter takes food from kitchen to tables")),
+
+ # not accurate, but the problems are real. avoid threads if you can
+ Slide("Threads (a caricature)",
+ Bullet(Bad("One waiter per table")),
+ SubBullet("Problems:",
+ Bullet(Bad("Expensive")),
+ Bullet(Bad("Waiters need to be careful not bump into each other")),
+ )),
+
+ # why threads are sometimes necessary
+ Slide("When do we want threads?",
+ Bullet("Long running, blocking operations"),
+ Bullet("Classic example: database access")),
+
+ # today we will discuss only (parts of) twisted.internet
+ Slide("Twisted: The Framework of Your Internet",
+ Image("twisted-overview.png")),
+
+ Slide("Project Stats",
+ Bullet("URL: ", URL("http://www.twistedmatrix.com")),
+ Bullet("License: LGPL"),
+ Bullet("Number of developers: approximately 20"),
+ Bullet("Version: 1.0.3"),
+ Bullet("Platforms: Unix, Win32"),
+ Bullet("Started in January 2000 by Glyph Lefkowitz")),
+
+ Slide("Part 2 - Basic Networking With Twisted"),
+
+ # quick review of how the internet works
+ Slide("Internet!",
+ Bullet("Network of interconnected machines"),
+ Bullet("Each machine has one (or more) IP addresses"),
+ Bullet("DNS maps names ('www.yahoo.com') to IPs (216.109.125.69)"),
+ Bullet("TCP runs on top of IP, servers listen on of of 65536 ports,"
+ " e.g. HTTP on port 80"),),
+
+ # we need to understand certain basic terms before we continue.
+ # the event loop is the last thing we run - it waits until
+ # an event occurs, then calls the appropriate handler.
+ Slide("Basic Definitions - Reactor",
+ Bullet("An object implementing the event loop",
+ PRE(EVENT_LOOP_CODE))),
+
+ Slide("Basic Definitions - Transport",
+ Bullet("Moves data from one location to another"),
+ Bullet("Main focus of talk are ordered, reliable byte stream transports"),
+ Bullet("Examples: TCP, SSL, Unix sockets"),
+ Bullet("UDP is a different kind of transport")),
+
+ # the client is the side which initiated the connection
+ # HTTP and SSH run on TCP-like transports, DNS runs on UDP or TCP
+ Slide("Basic Definitions - Protocol",
+ Bullet("Defines the rules for communication between two hosts"),
+ Bullet("Protocols communicate using a transport"),
+ Bullet("Typically there is a client, and server"),
+ Bullet("Examples: HTTP, SSH, DNS")),
+
+ Slide("All Together Now",
+ Bullet("The reactor gets events from the transports (read from network, write to network)"),
+ Bullet("The reactor passes events to protocol (connection lost, data received)"),
+ Bullet("The protocol tells the transport to do stuff (write data, lose connection)")),
+
+ # designing a new protocol is usually a bad idea, there are lots of
+ # things you can get wrong, both in design and in implementation
+ Slide("How To Implement A Protocol",
+ Bullet("Hopefully, you don't.")),
+
+ # XXX split into three expanded slides?
+ NumSlide("How To Not Implement A Protocol",
+ Bullet("Use an existing Twisted implementation of the protocol"),
+ Bullet("Use XML-RPC"),
+ Bullet("Use Perspective Broker, a remote object protocol")),
+
+ # connectionMade is called when connection is made
+ # dataReceived is called every time we receive data from the network
+ # connectionLost is called when the connection is lost
+ Slide("How To Really Implement A Protocol",
+ PRE(PROTOCOL_CODE)),
+
+ # factories - why?
+ Slide("Factories",
+ Bullet("A protocol instance only exists as long as the connection is there"),
+ Bullet("Protocols want to share state"),
+ Bullet("Solution: a factory object that creates protocol instances")),
+
+ # factory code - notice how protocol instances have access to the factory
+ # instance, for shared state. buildProtocol can return None if we don't
+ # want to accept connections from that address.
+ Slide("A Server Factory",
+ PRE(SERVER_CODE)),
+
+ # running the server we just wrote
+ Slide("Connecting A Factory To A TCP Port",
+ PRE(RUNNING_SERVER_CODE)),
+
+ # transport independence - using listenUNIX as example
+ Slide("Transport Independence",
+ Bullet("Notice how none of the protocol code was TCP specific"),
+ Bullet("We can reuse same protocol with different transports"),
+ Bullet("We could use listenUNIX for unix sockets with same code"),
+ Bullet("Likewise listenSSL for SSL or TLS")),
+
+ Slide("Client Side Protocol",
+ PRE(CLIENT_PROTOCOL_CODE)),
+
+ # client connections are different
+ Slide("Client Side Factories",
+ Bullet("Different requirements than server"),
+ Bullet("Failure to connect"),
+ Bullet("Automatic reconnecting"),
+ Bullet("Cancelling and timing out connections")),
+
+ # example client factory - explain use of default buildProtocol
+ Slide("Client Side Factories 2",
+ PRE(CLIENT_FACTORY_CODE)),
+
+ # connectTCP
+ Slide("Connection API",
+ PRE(CLIENT_CONNECT_CODE)),
+
+ # explain how transports buffer the output
+ Slide("Buffering",
+ Bullet("When we write to transport, data is buffered"),
+ Bullet("loseConnection will wait until all buffered data is sent, and producer (if any) is finished")),
+
+ # start/stopFactory
+ Slide("Factory Resources",
+ Bullet("Factories may want to create/clean up resources"),
+ Bullet("startFactory() - called on start of listening/connect"),
+ Bullet("stopFactory() - called on end of listening/connect"),
+ Bullet("Called once even if factory listening/connecting multiple ports")),
+
+ # example of restartable factory
+ Slide("Factory Resources 2",
+ PRE(FACTORY_START_CODE)),
+
+ Slide("Producers and Consumers",
+ Bullet("What if we want to send out lots of data?"),
+ Bullet("Can't write it out all at once"),
+ Bullet("We don't want to write too fast")),
+
+ Slide("Producers",
+ Bullet("Produce data for a consumer, in this case by calling transport's write()"),
+ Bullet("Pausable (should implement pauseProducing and resumeProducing methods)"),
+ Bullet("Push - keeps producing unless told to pause"),
+ Bullet("Pull - produces only when consumer tells it to")),
+
+ Slide("Consumers",
+ Bullet("registerProducer(producer, streaming)"),
+ Bullet("Will notify producer to pause if buffers are full")),
+
+ Slide("Sample Pull Producer",
+ PRE(PULL_PRODUCER_CODE)),
+
+ Slide("Sample Push Producer",
+ PRE(PUSH_PRODUCER_CODE)),
+
+ # scheduling events
+ Slide("Scheduling",
+ PRE(SCHEDULING_CODE)),
+
+ # pluggable reactors - why?
+ Slide("Choosing a Reactor - Why?",
+ Bullet("GUI toolkits have their own event loop"),
+ Bullet("Platform specific event loops")),
+
+ Slide("Choosing a Reactor",
+ Bullet("Twisted supports multiple reactors"),
+ Bullet("Default, gtk, gtk2, qt, win32 and others"),
+ Bullet("Tk and wxPython as non-reactors"),
+ Bullet("Reactor installation should be first thing code does")),
+
+ # example GUI client
+ Slide("Example GTK Program",
+ PRE(GUI_CODE)),
+
+ # you can learn more about
+ Slide("Learning more about networking and scheduling",
+ Bullet("twisted.internet.interfaces"),
+ Bullet("http://twistedmatrix.com/document/howtos/")),
+
+
+ Slide("Part 3 - Building Applications With Twisted"),
+
+ # the concept of the application
+ Slide("Applications",
+ Bullet("Reactor is a concept of event loop"),
+ Bullet("Application is higher-level"),
+ Bullet("Configuration, services, persistence"),
+ Bullet("Like reactor, you can listenTCP, connectTCP, etc.")),
+
+ # services concept
+ Slide("Services",
+ Bullet("Services can be registered with Application"),
+ Bullet("A service encapsulates 'business logic'"),
+ Bullet("Infrastructure outside the scope of protocols"),
+ Bullet("Examples: authentication, mail storage")),
+
+ # service example code
+ Slide("Services 2",
+ PRE(SERVICE_CODE)),
+
+ # logging
+ Slide("Logging",
+ PRE(LOGGING_CODE)),
+
+ # logging errors
+ # explain why this is good idea (twistd -b)
+ Slide("Logging Errors",
+ PRE(LOGGING_ERRORS_CODE)),
+
+ # twistd idea
+ Slide("twistd - Application Runner",
+ Bullet("Single access point for running applications"),
+ Bullet("Separate configuration from deployment")),
+
+ # twistd features
+ Slide("twistd Features",
+ Bullet("Daemonization"),
+ Bullet("Log file selection (including to syslog)"),
+ Bullet("Choosing reactor"),
+ Bullet("Running under debugger"),
+ Bullet("Profiling"),
+ Bullet("uid, gid"),
+ Bullet("Future: WinNT Services")),
+
+ # making modules for twistd -y
+ Slide("Making a runnable application",
+ PRE(RUNNABLE_APP_CODE)),
+
+ # running the server
+ Slide("Running twistd",
+ PRE(TWISTD_CODE)),
+
+ Slide("Part 4: Further Bits and Pieces"),
+
+ Slide("Other twisted.internet Features",
+ Bullet("UDP, Multicast, Unix sockets, Serial"),
+ Bullet("Thread integration")),
+
+ Slide("Deferreds",
+ Bullet("Deferred - a promise of a result"),
+ Bullet("Supports callback chains for results and exceptions"),
+ Bullet("Used across the whole framework"),
+ Bullet("Make event-driven programming much easier"),
+ Bullet("Can work with asyncore too, not just Twisted")),
+
+ Slide("Protocol implementations",
+ Bullet("Low-level implementations, without policies"),
+ Bullet("SSH, HTTP, SMTP, IRC, POP3, telnet, FTP, TOC, OSCAR, SOCKSv4, finger, DNS, NNTP, IMAP, LDAP"),
+ Bullet("Common GPS modem protocols")),
+
+ Slide("Frameworks",
+ Bullet("twisted.web - Web server framework"),
+ Bullet("twisted.news - NNTP server framework"),
+ Bullet("twisted.words - messaging framework"),
+ Bullet("twisted.names - DNS server")),
+
+ Slide("Perspective Broker",
+ Bullet("Object publishing protocol"),
+ Bullet("Fast, efficient and extendable"),
+ Bullet("Two-way, asynchronous"),
+ Bullet("Secure and encourages secure model"),
+ Bullet("Implemented in Python for Twisted, and Java")),
+
+ Slide("Lore",
+ Bullet("Simple documentation system"),
+ Bullet("Simple subset of XHTML"),
+ Bullet("Generates LaTeX, XHTML")),
+
+ Slide("Reality",
+ Bullet("Multiplayer text simulation framework"),
+ Bullet("Original source of Twisted project"),
+ Bullet("Now a totally different project")),
+)
+
+
+if __name__ == '__main__':
+ lecture.renderHTML(".", "twisted_internet-%02d.html", css="main.css")
diff --git a/doc/historic/2003/pycon/twisted-reality/componentized.svg b/doc/historic/2003/pycon/twisted-reality/componentized.svg
new file mode 100644
index 0000000..613192a
--- /dev/null
+++ b/doc/historic/2003/pycon/twisted-reality/componentized.svg
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
+<svg
+ id="svg137"
+ sodipodi:version="0.31"
+ width="11in"
+ height="8in"
+ sodipodi:docbase="/home/washort/projects/PyCon/"
+ sodipodi:docname="/home/washort/projects/PyCon/adapters2.svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs
+ id="defs139" />
+ <sodipodi:namedview
+ id="base"
+ snaptoguides="false"
+ showgrid="false" />
+<g>
+<animateTransform attributeName="transform" attributeType="XML" begin="1s" dur="3s" values="1,1;1.5,1.5" type="scale" fill="freeze" additive="sum" />
+<animateMotion begin="1s" dur="3s" fill="freeze" path="M 0 0 L -40 -300"/>
+<g style="fill-rule:evenodd;stroke-width:10;stroke:#000000;stroke-opacity:1;stroke-dasharray:none;stroke-linejoin:bevel;fill-opacity:1;">
+<animate attributeName="stroke-opacity" attributeType="CSS" begin="1s" dur="3s" from="1" to="0.4" fill="freeze"/>
+<animate attributeName="fill-opacity" attributeType="CSS" begin="1s" dur="3s" from="1" to="0.4" fill="freeze"/>
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#3e62db"
+ id="polygon152"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,462.812,244.007)" />
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#d86264"
+ id="polygon234"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,634.521,144.935)" />
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#d86264"
+ id="polygon243"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,634.604,342.45)" />
+
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;;fill:#d86264"
+ id="polygon252"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,290.377,145.564)" />
+
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#d86264"
+ id="polygon261"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,462.169,442.179)" />
+
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#d86264"
+ id="polygon270"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,462.812,45.8392)" />
+
+ <g style="stroke-width:2pt">
+ <path
+ d="M 538.285 387.439 L 571.873 407.105 "
+ id="path372"
+ transform="translate(171.25,-20)" />
+ <path
+ d="M 538.285 308.11 L 572.315 288.223 "
+ id="path373"
+ transform="translate(171.25,-20)" />
+ <path
+ d="M 365.044 287.781 L 400.399 308.552 "
+ id="path376"
+ transform="translate(171.25,-20)" />
+ <path
+ d="M 468.9 465.883 L 468.9 426.993 "
+ id="path377"
+ transform="translate(171.25,-20)" />
+ <path
+ d="M 469.342 268.335 L 469.342 229.445 "
+ id="path378"
+ transform="translate(171.25,-20)" /></g>
+</g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-opacity:0.398649;stroke-width:2pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1;"
+ d="M 365.928 407.547 L 399.957 387.66 "
+ id="path379"
+ transform="translate(171.25,-20)" />
+<polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#d86264;fill-rule:evenodd;stroke-width:10;stroke:#000000;stroke-opacity:1;stroke-dasharray:none;stroke-linejoin:bevel;fill-opacity:1;"
+ id="polygon225"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,290.836,343.189)" />
+<g style="fill:#000000;stroke:none;font-family:URW Gothic L;font-style:normal;font-weight:normal;font-size:36;fill-opacity:1;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;text-anchor:start;writing-mode:lr;" >
+<animate attributeName="fill-opacity" attributeType="CSS" begin="1s" dur="3s" from="1" to="0.4" fill="freeze"/>
+ <text
+ x="594.546"
+ y="312.197"
+ id="text383">
+ <tspan
+ x="594.546"
+ y="312.197"
+ sodipodi:role="line"
+ id="tspan476">
+Thing</tspan>
+ <tspan
+ x="594.546"
+ y="348.197"
+ sodipodi:role="line"
+ id="tspan478">
+</tspan>
+ </text>
+ <text
+ x="394.315"
+ y="230.363"
+ id="text388">
+ <tspan
+ x="394.315"
+ y="230.363"
+ sodipodi:role="line"
+ id="tspan504">
+Weapon</tspan>
+ </text>
+
+ <text
+ x="569.441"
+ y="524.279"
+ id="text396">
+ <tspan
+ x="569.441"
+ y="524.279"
+ sodipodi:role="line"
+ id="tspan460">
+Portable</tspan>
+ </text>
+ <text
+ style="font-size:18;font-style:italic"
+ x="561.266"
+ y="347.885"
+ id="text480">
+ <tspan
+ x="561.266"
+ y="347.885"
+ sodipodi:role="line"
+ id="tspan496">
+(Componentized)</tspan>
+ </text>
+
+ <text
+ style="font-size:18;font-style:italic"
+ x="594.854"
+ y="548.526"
+ id="text498">
+ <tspan
+ x="594.854"
+ y="548.526"
+ sodipodi:role="line"
+ id="tspan499">
+(Adapter)</tspan>
+ </text>
+ <text
+ style="font-size:18;font-style:italic"
+ x="422.496"
+ y="254.194"
+ id="text501">
+ <tspan
+ x="422.496"
+ y="254.194"
+ sodipodi:role="line"
+ id="tspan502">
+(Adapter)</tspan>
+ </text>
+ </g>
+<text
+ style="fill:#000000;stroke:none;font-family:URW Gothic L;font-style:italic;font-weight:normal;font-size:18;text-anchor:start;writing-mode:lr;fill-opacity:1;troke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;"
+ x="422.496"
+ y="451.299"
+ id="text489">
+ <tspan
+ x="422.496"
+ y="451.299"
+ sodipodi:role="line"
+ id="tspan494">
+(Adapter)</tspan>
+ </text>
+ <text
+ style="fill:#000000;stroke:none;font-family:URW Gothic L;font-style:roman;font-weight:normal;font-size:26;text-anchor:start;writing-mode:lr;fill-opacity:1;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;"
+ x="388.457"
+ y="430.303"
+ id="text393">
+ <tspan
+ x="388.457"
+ y="430.303"
+ sodipodi:role="line"
+ id="tspan458">
+Merchandise</tspan>
+ </text>
+</g>
+</svg>
diff --git a/doc/historic/2003/pycon/twisted-reality/twisted-reality.html b/doc/historic/2003/pycon/twisted-reality/twisted-reality.html
new file mode 100644
index 0000000..bb96500
--- /dev/null
+++ b/doc/historic/2003/pycon/twisted-reality/twisted-reality.html
@@ -0,0 +1,578 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html>
+ <head>
+ <title>Twisted Reality: A Flexible Framework for Virtual Worlds</title>
+ <link href="stylesheet.css" type="text/css" rel="stylesheet" />
+ </head>
+ <body>
+ <h1>Twisted Reality: A Flexible Framework for Virtual Worlds</h1>
+
+ <ul>
+ <li>Allen Short
+<a href="washort@twistedmatrix.com">&lt;washort@twistedmatrix.com&gt;</a></li>
+ <li>Glyph Lefkowitz
+ <a href="glyph@twistedmatrix.com">&lt;glyph@twistedmatrix.com&gt;</a></li>
+ </ul>
+
+ <h2>Abstract</h2>
+ <p>Flexibly modelling virtual worlds in object-oriented languages has
+ historically been difficult; the issues arising from multiple
+ inheritance and order-of-execution resolution have limited the
+ sophistication of existing object-oriented simulations. Twisted
+ Reality avoids these problems by reifying both actions and
+ relationships, and avoiding inheritance in favor of automated
+ composition through adapters and interfaces.</p>
+
+ <h2>Motivation</h2>
+
+ <p>Text-based simulations have a long and venerable history, from
+ games such as Infocom's Zork and Bartle's MUD to modern systems
+ such as Inform, LambdaMOO and Cold. The general trend in the
+ development of these systems has been toward domain-specific
+ languages, which has largely been an improvement. However, a
+ discrepancy remains between systems for single-user and
+ multiple-user simulations: in single-user systems such as Inform,
+ incremental extensibility has been sacrificed to allow for complex
+ interaction with the world; whereas in multiple-user systems,
+ incremental extensibility is paramount, but it is achieved at the
+ cost of a much simpler model of interaction. Twisted Reality aims
+ to bring the sophistication of Inform's action model to multiuser
+ simulation.</p>
+
+
+<h2>The Twisted Component Model</h2>
+
+<p>Twisted's component system is almost identical to Zope 3's. The
+primary element is the interface, a class used as a point of
+integration and documentation. Classes may declare the interfaces they
+implement by setting their <code class="python">__implements__</code>
+attribute to a tuple of interfaces. Additional interfaces may be added
+to classes with <code
+class="python">registerAdapter(adapterClass,originalClass,interface)</code>;
+when <code class="python">getAdapter(obj, interfaceClass)</code> is
+called on an object, the adapter associated with that interface and
+class is looked up and instantiated as a wrapper around <code
+class="python">obj</code>. (Alternately, if <code
+class="python">obj</code> implements the requested interface, the
+original object is simply returned.)</p>
+
+<h3>Componentized</h3>
+<p>In addition to the basic system of adapters and interfaces, Twisted
+has the <code class="python">Componentized</code> class. Instances of
+<code class="python">Componentized</code> hold instances of their
+adapters. This storage of adapter instances encourages separation of
+concerns; multiple related instances representing aspects of a
+simulation object can be automatically composed in a single
+Componentized instance.</p>
+
+<p><code class="python">Componentized</code> is the heart of Twisted
+Reality; it is subclassed by <code class="python">Thing</code>, the
+base class for all simulation objects. Functionality is added to
+<code class="python">Thing</code>s with adapters; for example, the
+<code class="python">Portable</code> adapter adds the abilities to be
+picked up and dropped. </p>
+
+<p>By separating aspects of the simulation object into multiple
+instances, several improvements in ease of code maintenance can be
+realized. Persistence of simulation objects, for example, is greatly
+eased by <code class="python">Componentized</code>: each adapter's
+state can be stored in a separate database table or similar data
+store.</p>
+
+<h2>Parsing System</h2>
+
+<p>The key element missing from multiuser simulations' parsing systems
+is an abstract representation of actions. Current systems proceed
+directly from parsing the user's input to executing object-specific
+code. For example, LambdaMOO, one of the most popular object-oriented
+simulation frameworks, handles input using a non-customizable lexer
+which dispatches to parsing methods on simulation objects. The
+ColdCore framework, a similar effort, improves on this model by
+providing pattern-matching facilities for the lexer, but performs
+dispatch in essentially the same fashion. In contrast to these
+systems, Twisted Reality separates parsing from simulation objects
+entirely, keeping a global registry of parser methods which produce
+objects representing actions, rather than directly performing the
+actions. Adding this layer allows for more sophisticated parsing and
+sensitivity to ambiguity.</p>
+
+<p>The parser in <code class="python">reality.text.english</code> uses
+a relatively simple strategy: it keeps a parser registry which maps
+<q>verbs</q> (i.e., substrings at the beginning of the user input) to
+parser methods, and runs all methods whose prefixes match the input,
+collecting the actions they return. Parsing methods are added to the
+system by registering <code class="python">Subparser</code>s. </p>
+
+<pre class="python">class MusicParser(english.Subparser):
+ def parse_blow(self, player, instrumentName):
+ actor = player.getComponent(IPlayWindInstrumentActor)
+ if actor is None:
+ return []
+ return [PlayWindInstrument(actor, instrumentName)]
+
+english.registerSubparser(MusicParser())</pre>
+
+<p><code class="python">english.registerSubparser</code> collects
+ methods prefixed with <code class="python">parse_</code> from
+ the subparser and places them in the parsing registry.</p>
+
+<pre class="shell">a Room
+You see a rocket, a whistle, and a candle.
+Exits: a door, north
+bob: <b>blow whistle</b>
+You play a shrill blast upon a whistle.</pre>
+
+<p>Here is one of the simplest cases for the parser: <q><code
+class="shell">blow whistle</code></q> should obviously resolve to a
+single action, in this case <code class="python"
+>PlayWindInstrument</code>.</p>
+
+<p>The parser calls <code class="python">MusicParser.parse_blow</code>
+with the actor and the remainder of the input, and adds the list of
+actions it returns to the collection of possible actions. If only one
+action is possible, it immediately dispatches it. This strategy allows
+the parser to examine the state of the simulation before committing to
+a decision about what the player means. For example, the check for the
+actor interface is a simple form of permissions; if you don't
+implement the required interface, you aren't allowed to perform the
+action.</p>
+
+<p>Since this sort of parser is quite common, it has been generalized
+ to a simple mapping of command names to actions:</p>
+<pre class="python">class FireParser(english.Subparser):
+ simpleTargetParsers = {"blow": Extinguish}
+
+english.registerSubparser(FireParser())</pre>
+<pre class="shell">bob: <b>blow candle</b>
+You blow out a candle.
+</pre>
+<p>The real test of any parsing system of this nature, of course, is
+its ability to handle ambiguity. Since two possibilities for
+parsing a command starting with <q>blow</q> now exist, the parser has two
+potential actions to examine: <code class="python"
+>PlayWindInstrument</code> and <code class="python"
+>Extinguish</code>. Obviously, only <code class="python"
+>Extinguish</code> makes sense, and the parser determines this by
+examining the interfaces on the targets and rejecting actions for
+which the target is invalid.</p>
+
+<pre class="python">class ExplosivesParser(english.Subparser):
+ simpleToolParsers = {"blow": BlowUp}
+
+english.registerSubparser(ExplosivesParser())
+</pre>
+<pre class="shell">bob: <b>blow door</b>
+You fire a rocket at a door.
+*BOOM*!!
+The door shatters to pieces!
+</pre>
+
+<p> The other common case is actions with three participants -- actor,
+target, and tool. The parser generated here is intelligent enough to
+look around for an appropriate tool (again, by examining interfaces)
+and include it in the action.</p>
+
+<p>Despite these techniques for disambiguating the user's meaning,
+situations will inevitably arise where multiple actions are equally
+valid parses. In these cases, the parser formats the list of potential
+actions and presents the choices to the user.</p>
+
+<pre class="shell">You see a short sword, and a long sword.
+bob: <b>get sword</b>
+Which Target?
+1: long sword
+2: short sword
+bob: <b>1</b>
+You take a long sword.</pre>
+
+<h2>Actions System</h2>
+
+
+<p>Actions in Twisted Reality, as in Inform, are objects representing
+a successful parse of a player's intentions. Actions are classified
+according to the number of objects they operate upon: <code
+class="python">NoTargetAction</code> (actions such as <code
+class="python">Say</code> or <code class="python">Look</code>), <code
+class="python">TargetAction</code> (e.g. <code
+class="python">Eat</code>, <code class="python">Wear</code>), <code
+class="python">ToolAction</code> (e.g. <code
+class="python">Open</code>, <code class="python">Take</code>). When
+actions are defined, interfaces corresponding to the possible roles in
+the action are also created. When an action is instantiated, it asks
+the participants in the action to adapt themselves to the actor,
+target, or tool interfaces, as appropriate. When dispatched, the
+action may call handler methods on the adapted objects or dispatch
+subsidiary actions.</p>
+
+<pre class="python">IDamageActor = things.IThing
+class Damage(actions.ToolAction):
+ def formatToActor(self):
+ with = ""
+ if self.tool:
+ with = " with ", self.tool
+ return ("You hit ",self.target) + with + (".",)
+ def formatToTarget(self):
+ with = ()
+ if self.tool:
+ with = " with ", self.tool
+ return (self.actor," hits you") + with + (".",)
+ def formatToOther(self):
+ with = ""
+ if self.tool:
+ with = " with ", self.tool
+ return self.actor," hits ",self.target) + with + (".",)
+ def doAction(self):
+ amount = self.tool.getDamageAmount()
+ self.target.damage(amount)
+
+class Weapon(components.Adapter):
+ __implements__ = IDamageTool
+ def getDamageAmount(self):
+ return 10
+
+class Damageable(components.Adapter):
+ __implements__ = IDamageTarget
+ def damage(self, amount):
+ self.original.emitEvent("Ow! that hurt. You take %d points of damage."
+ % amount, intensity=1)
+class HarmParser(english.Subparser):
+ simpleToolParsers = {"hit":Damage}
+
+english.registerSubparser(HarmParser())
+components.registerAdapter(Damageable, things.Actor, IDamageTarget)</pre>
+
+<p><code class="python">actions.ToolAction</code>, via metaclass
+magic, creates three interfaces when subclasssed, named after the
+subclass: in this case, <code class="python">IDamageActor</code>,
+<code class="python" >IDamageTarget</code>, and <code
+class="python">IDamageTool</code>. However, since <code
+class="python">IDamageActor</code> already exists, the metaclass does
+ not clobber it. Setting <code class="python">IDamageActor</code> to
+<code class="python">IThing</code> indicates that any <code
+class="python">Thing</code> may perform the <code
+ class="python">Damage</code> action. The other elements of the action
+are represented here by <code class="python">Weapon</code> and <code
+class="python">Damageable</code> as the tool and the target,
+respectively. The <code class="python">HarmParser</code> adds a
+<q>hit</q> command, and the call to <code
+class="python">registerAdapter</code> ensures that any <code
+class="python">Actor</code>s who do not already have a
+component implementing <code class="python">IDamageTarget</code> will
+receive a <code class="python">Damageable</code> when needed.
+</p>
+<pre class="python">room = ambulation.Room("room")
+bob = things.Actor( "Bob")
+rodney = things.Actor("rodneY")
+sword = things.Movable("sword")
+
+sword.addAdapter(conveyance.Portable, True)
+sword.addAdapter(harm.Weapon, True)
+
+
+for o in rodney, bob, sword:
+ o.moveTo(room)
+</pre>
+
+<p>In this example, we create instances of <code
+class="python">Movable</code> <code class="python">Actor</code>
+(subclasses of <code class="python">Thing</code>), a <code
+class="python">Room</code>, then adds a <code
+class="python">Portable</code> adapter to the sword, allowing it to be
+picked up and dropped, as well as a <code class="python">Weapon</code>
+adapter, and finally moves all three into the room.</p>
+
+<pre class="shell">a room
+You see rodneY, and a sword.
+Bob: <b>get sword</b>
+You take a sword.
+Bob: <b>hit rodney with sword</b>
+You hit rodneY with a sword.</pre>
+
+<p>The parser instantiates the <code class="python">Damage</code>
+action with Bob, Rodney, and the sword as actor, target, and tool. The
+action is dispatched, calling <code
+class="python">Damage.doAction</code>, which inflicts damage upon
+Rodney. From Rodney's perspective:</p>
+
+<pre class="shell">a room
+You see Bob, and a sword.
+Bob takes a sword.
+Bob hits you with a sword.
+Ow! that hurt. You take 10 points of damage.
+rodneY:</pre>
+
+<p>The primary advantage of this actions system is that it provides a
+central point for dispatching object-specific behaviour in a
+customizable manner. This mechanism prevents order-of-execution
+problems: in other simulations of this type, combining multiple game
+effects is difficult since the connections between them are not made
+explicit. When confronted with ambiguity, TR's action system refuses
+to guess: all combinations of effects that make sense must be
+implemented separately. The Adapters system makes this manageable even
+in the face of arbitrarily extended complexity.</p>
+
+<p>Also, it allows for centralized handling of string formatting,
+instead of having each actor or target handle output of event
+descriptions. For example, suppose there is a zone prohibiting PvP
+combat. The <code class="python">Damage</code> action can suppress the
+usual messages describing combat (as well as the actual damage
+routines) since it is responsible for generating them.</p>
+
+
+<h2>Composing Simulations with Adapters</h2>
+
+<p>The combination of these features -- an incrementally extendable
+parser, actions as first-class objects, componentized simulation
+objects -- provide a powerful basis for the composition of simulations
+within a virtual world, often enabling extensions to the world and
+object behaviour without touching unrelated code. For example, to add
+armor that reduces damage to the simple combat simulation described
+above, we add an <code class="python">Armor</code> class which
+forwards the <code class="python">IDamageTarget</code> interface:</p>
+<pre class="python">class Armor(raiment.Wearable):
+ __implements__ = IDamageTarget, raiment.IWearTarget, raiment.IUnwearTarget
+ originalTarget = None
+ armorCoefficient = 0.5
+ def dress(self, wearer):
+ originalTarget = wearer.getComponent(IDamageTarget)
+ if originalTarget:
+ self.originalTarget = originalTarget
+ wearer.original.setComponent(IDamageTarget, self)
+
+ def undress(self, wearer):
+ if self.originalTarget:
+ wearer.setComponent(IDamageTarget, self.originalTarget)
+
+ def damage(self, amount):
+ self.original.emitEvent("Your armor cushions the blow.", intensity=2)
+ if self.originalTarget:
+ self.originalTarget.damage(amount * self.armorCoefficient)</pre>
+
+<p><code class="python">Armor</code> inherits from the <code
+class="python">Wearable</code> adapter, and thus receives notification
+of the player wearing or removing it. When this happens, it forwards
+or unforwards the <code class="python">damage</code> method,
+respectively.</p>
+<pre class="shell">a room
+You see an armor, Bob, and a sword.
+rodneY: <b>take armor</b>
+You take an armor.
+rodneY: <b>wear armor</b>
+You put on an armor.
+Bob hits you with a sword.
+Your armor cushions the blow.
+Ow! that hurt. You take 5 points of damage.</pre>
+
+<p>In this fashion, the combat simulation can be extended to deal with
+various types of weapons, armor, damageable objects, and types of
+damage, with little or no changes to existing code.</p>
+
+<p> Now, let us consider a second type of simulation common to virtual
+ worlds: shops. We wish to prevent unpaid items from leaving the shop,
+ and to have a price associated with each item.</p>
+
+<pre class="python">
+class IVendor(components.Interface): pass
+class IMerchandise(components.Interface): pass
+
+class Buy(actions.TargetAction):
+ def formatToOther(self):
+ return ""
+ def formatToActor(self):
+ return ("You buy ",self.target," from ",self.vendor," for ",
+ self.target.price," zorkmids.")
+
+ def doAction(self):
+ vendors = self.actor.original.lookFor(None, IVendor)
+ if vendors:
+ #assume only one vendor per room, for now
+ self.vendor = vendors[0]
+ else:
+ raise errors.Failure("There appears to be no shopkeeper here "
+ "to receive your payment.")
+ amt = self.target.price
+ self.actor.withdraw(amt)
+ self.vendor.buy(self.target, amt)
+
+class ShopParser(english.Subparser):
+ simpleTargetParsers = {"buy": Buy}
+english.registerSubparser(ShopParser())</pre>
+
+<p>The basic behaviour for buying an object in a shop is simple:
+first, a vendor is located, the price is looked up, then money is
+transferred from the buyer's account to the vendor's.</p>
+
+<pre class="python">class Customer(components.Adapter):
+ __implements__ = IBuyActor
+
+ def withdraw(self, amt):
+ "interface to accounting system goes here"
+
+class Vendor(components.Adapter):
+ __implements__ = IVendor
+
+ def shoutPrice(self, merch, cust):
+ n = self.getComponent(english.INoun)
+ title = ('creature', 'sir','lady'
+ )[cust.getComponent(things.IThing).gender]
+ merchName = merch.original.getComponent(english.INoun).name))
+ self.original.emitEvent('%s says "For you, good %s, only %d '
+ 'zorkmids for this %s."' % (n.nounPhrase(cust),
+ title, merch.price,
+ merchName))
+
+ def buy(self, merchandise, amount):
+ self.deposit(amount)
+ merchandise.original.removeComponent(merchandise)
+
+ def stock(self, obj, price):
+ m = Merchandise(obj)
+ m.price = price
+ m.owner = self
+ m.home = self.original.location
+ obj.addComponent(m, ignoreClass=1)
+
+ def deposit(self, amt):
+ "more accounting code"</pre>
+<p>The essential operations for management of shop inventory are
+<code class="python">Vendor.stock</code> and <code
+class="python">Vendor.buy</code>, which add and remove a <code
+class="python">Merchandise</code> adapter, which stores the
+state related to the shop simulation for the object (in this case, its
+price, its owner, and the location it lives).</p>
+
+<pre class="shell">A weapons shop. You see a long sword, and Asidonhopo.
+Exits: a Secret Trapdoor, down; a Security Door, north
+bob: <b>get sword</b>
+You take a long sword.
+Asidonhopo says "For you, good sir, only 100 zorkmids for this long sword."
+</pre>
+
+<p>To enforce our anti-theft policy, we put constraints on the exits
+to the shop.</p>
+<pre class="python">
+class ShopDoor(ambulation.Door):
+ def collectImplementors(self, asker, iface, collection, seen,
+ event=None, name=None, intensity=2):
+ if iface == ambulation.IWalkTarget:
+ unpaidItems = asker.searchContents(None, IMerchandise)
+ if unpaidItems:
+ collection[self] = things.Refusal(self, "You cant leave, "
+ "you haven't paid!")
+ return
+
+ ambulation.Door.collectImplementors(self, asker, iface,
+ collection, seen, event,
+ name, intensity)
+ return collection</pre>
+
+<p><code class="python">collectImplementors</code> is the means by
+which queries for action participants are accomplished. It is a rather
+general graph-traversal mechanism and thus takes a few arguments:
+<code class="python">asker</code> is the object that initiated the
+query. <code class="python">iface</code> is the interface the results
+must conform to, <code class="python">collection</code> is the results
+so far, and <code class="python">seen</code> is a collection of
+objects already visited. The check done here is fairly simple: it
+refuses queries for <code class="python">IWalkTarget</code>s (the
+interface needed for walking between rooms) if the asker contains
+things that implement <code class="python">IMerchandise</code>, in
+particular unpaid items. Otherwise, it passes on the query to its
+superclass.</p>
+
+<pre class="shell">bob: <b>go north</b>
+You cant leave, you haven't paid!</pre>
+
+<p>Here, the <q>Security Door</q> examines the actor's contents for
+objects implementing IMerchandise. Since the sword still has a
+Merchandise adapter attached, the passage is barred.</p>
+
+<pre class="shell">bob: <b>go down</b></pre>
+
+<p>However, relying on the exits
+to contain merchandise is potentially error-prone; it demands knowing
+about all forms of locomotion in advance. If an unsecured exit from
+ the shop exists, or the player has the ability to teleport,
+ this form of security can be bypassed. Therefore, it is
+advantageous to have the Merchandise adapter itself keep the item
+within the shop.</p>
+
+<pre class="python">class Merchandise(components.Adapter):
+ __implements__ = IMerchandise, things.IMoveListener, IBuyTarget
+
+ def thingArrived(*args):
+ pass
+ def thingLeft(*args):
+ pass
+ def thingMoved(self, emitter, event):
+ if self.original == emitter and isinstance(event, conveyance.Take):
+ self.owner.shoutPrice(self, self.original.location)
+ if self.original.getOutermostRoom() != self.home:
+ self.original.emitEvent("The %s vanishes with a *foop*."
+ % self.getComponent(english.INoun).name)
+ self.original.moveTo(self.home)</pre>
+
+<p>When objects move, they broadcast events to nearby things
+ (where <q>nearby</q> is determined, again, by <code
+ class="python">collectImplementors</code>) that implement
+ <code class="python">IMoveListener</code>. In this case, the
+ <code class="python">Merchandise</code> adapter <q>listens</q>
+ for being picked up, and prompts the shopkeeper to quote the
+ price, and also checks to make sure it is contained by its
+ home room. If the player manages to leave the shop with unpaid
+ merchandise --</p>
+
+<pre class="shell">The long sword vanishes with a *foop*.</pre>
+
+<p>then it sets its location to its home room and informs the prospective
+shoplifter he no longer has his prize.</p>
+
+<h2>Future Directions</h2>
+
+<p>Current development efforts focus on enlarging the standard library
+of simulation objects and behaviour, developing web-based interfaces
+to the simulation, and improving the persistence layer. Possible
+extensions include client-side generation of action objects, enabling
+the development of graphical interfaces, or adapting the text system
+to other languages than English.</p>
+
+<h2>Conclusions</h2>
+
+<p>As seen in these examples, Twisted Reality provides features not
+found in other object-oriented simulation frameworks. The component
+model allows automatic aggregation of related objects; the actions
+system provides a mechanism for precise control of game effects; and
+the parser enables incremental extension of user input
+handling. Combined, they provide a powerful basis for modelling
+virtual worlds by composing simulations.</p>
+
+<h2>Acknowledgements</h2>
+<p>Thanks to Chris Armstrong and Donovan Preston for contributions to
+ Twisted Reality, and to Ying Li for editorial assistance.</p>
+
+<h2>References</h2>
+<ul>
+<li>Jason Asbahr, <a
+ href="http://asbahr.com/paper1html/paper1.html">Beyond: A
+ Portable Virtual World Simulation Framework</a>,
+ <i>Proceedings of the Seventh International Python
+ Conference</i> (1998).</li>
+
+<li>Pavel Curtis, <a
+ href="ftp://ftp.lambda.moo.mud.org/pub/MOO/ProgrammersManual.html"><i>LambdaMOO programmer's manual</i></a>, 1997.</li>
+<li> Jim Fulton, <a href="?">Zope Component Architecture</a></li>
+<li> Brandon Gillespie, <i><a
+ href="http://ice.cold.org:1180/bin/help?node=coldc">ColdC Reference Manual</a></i>, 2001.</li>
+<li>Glyph Lefkowitz, and Moshe Zadka, <q><a
+ href="http://twistedmatrix.com/doc/historic/ipc10paper">The Twisted Network Framework</a></q>, <i>Proceedings of the Tenth International Python Conference</i> (2002): 83.</li>
+<li>Graham Nelson, <i><a href="http://www.inform-fiction.org/manual/about_dm4.html">The
+ Inform Designer's Manual</a></i>. 4th ed. (St Charles, IL:
+ Interactive Fiction Library, 2001).</li>
+
+ </ul>
+
+ </body>
+</html>
diff --git a/doc/historic/2004/ibm/talk.html b/doc/historic/2004/ibm/talk.html
new file mode 100644
index 0000000..56d1bad
--- /dev/null
+++ b/doc/historic/2004/ibm/talk.html
@@ -0,0 +1,495 @@
+<html><head><title>Twisted: A Tutorial</title></head><body>
+
+<h1>Twisted: A Tutorial</h1>
+
+<h2>Thanks</h2>
+
+<p>I am grateful to IBM for inviting me to talk here, and to Muli Ben-Yehuda for arranging everything.</p>
+
+<h2>Administrative Notes</h2>
+
+<p>After reading Peter Norvig's infamous <q>The Gettysburg Powerpoint Presentation</q>, I was traumatized enough to forgoe the usual bullets and slides style, which originally was developed for physical slide projectors. Therefore, these notes are presented as one long HTML file, and I will use a new invention I call the <q>scrollbar</q> to show just one thing at a time. Enjoy living on the bleeding edge of presentation technology!</p>
+
+<h2>What Is Twisted?</h2>
+
+<p>Twisted is an <em>event-based networkings framework for Python</em>. It includes not only the basics of networking but also high-level protocol implementations, scheduling and more. It uses Python's high-level nature to enable few dependencies between different parts of the code. Twisted allows you to write network applications, clients and servers, without using threads and without running into icky concurrency issues.</p>
+
+<blockquote>
+A computer is a state machine.
+Threads are for people who can't program state machines.
+</blockquote>
+
+<p>Alan Cox in a discussion about the threads and the Linux scheduler</p>
+<p>http://www.bitmover.com/lm/quotes.html</p>
+
+<h2>An Extremely Short Introduction to Python</h2>
+
+<p>Python is a high-level dyanmically strongly typed language. All values are references to objects, and all arguments passed are objects references. Assignment changes the reference a variable points to, not the reference itself. Data types include integers (machine sized and big nums) like <code>1</code> and <code>1L</code>, strings and unicode strings like <code>"moshe"</code> and <code>u"\u05DE\u05E9\u05D4 -- moshe"</code>, lists (variably typed arrays, really) like <code>[1,2,3, "lalala", 10L, [1,2]]</code>, tuples (immutable arrays) like <code>("1", 2, 3)</code>, dictionaries <code>{"moshe": "person", "table": "thing"}</code> and user-defined objects.</p>
+
+<p>Every Python object has a type, which is itself a Python object. Some types aare defined in native C code (such as the types above) and some are defined in Python using the class keyword.</p>
+
+<p>Structure is indicated through indentation.</p>
+
+<p>Functions are defined using</p>
+
+<pre class="py-listing">
+def function(param1, param2, optionalParam="default value", *restParams,
+ **keywordParams):
+ pass
+</pre>
+
+<p>And are called using <code>function("value for param1", param2=42,
+optionalParam=[1,2], "these", "params", "will", "be", "restParams",
+keyword="arguments", arePut="in dictionary keywordParams")</code>.</p>
+
+<p>Functions can be defined inside classes:</p>
+
+<pre class="py-listing">
+class Example:
+ # constructor
+ def __init__(self, a=1):
+ self.b = a
+ def echo(self):
+ print self.b
+e = Example(5)
+e.echo()
+</pre>
+
+<p>All methods magically receive the self argument, but must treat it
+explicitly.</p>
+
+<p>Functions defined inside functions enjoy lexical scoping. All variables
+are outer-scope unless they are assigned to in which case they are inner-most
+scope.</p>
+
+<h2>How To Use Twisted</h2>
+
+<p>Those of you used to other event-based frameworks (notably, GUIs) will recognize the familiar pattern -- you call the framework's <code>mainloop</code> function, and it calls registered event handlers. Event handlers must finish quickly, to enable the framework to call other handlers without forcing the client (be it a GUI user or a network client) to wait. Twisted uses the <code>reactor</code> module for the main interaction with the network, and the main loop function is called <code>reactor.run</code>. The following code is the basic skeleton of a Twisted application.</p>
+
+<pre class="py-listing">
+from twisted.internet import reactor
+reactor.run()
+</pre>
+
+<p>This runs the reactor. This takes no CPU on UNIX-like systems, and little CPU on Windows (some APIs must be busy-polled), runs forever and does not quit unless delivered a signal.</p>
+
+<h2>How To Use Twisted to Do Nothing</h2>
+
+<p>Our first task using Twisted is to build a server to the well-known <q>finger</q> protocol -- or rather a simpler variant of it. The first step is accepting, and hanging, connections. This example will run forever, and will allow clients to connect to port 1079. It will promptly ignore everything they have to say...</p>
+
+
+<pre class="py-listing">
+from twisted.internet import protocol, reactor
+class FingerProtocol(protocol.Protocol):
+ pass
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>The protocol class is empty -- the default network event handlers simply throw away the events. Notice that the <code>protocol</code> attribute in <code>FingerFactory</code> is the <code>FingerProtocol</code> class itself, not an instance of it. Protocol logic properly belongs in the <code>Protocol</code> subclass, and the next few slides will show it developing.</p>
+
+
+<h2>How To Use Twisted to Do Nothing (But Work Hard)</h2>
+
+<p>The previous example used the fact that the default event handlers in the protocol exist and do nothing. The following example shows how to code the event handlers explicitly to do nothing. While being no more useful than the previous version, this shows the available events.</p>
+<pre class="py-listing">
+from twisted.internet import protocol, reactor
+class FingerProtocol(protocol.Protocol):
+ def connectionMade(self):
+ pass
+ def connectionLost(self):
+ pass
+ def dataReceived(self, data):
+ pass
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>This example is much easier to work with for the copy'n'paste style of programming...it has everything a good network application has: a low-level protocol implementation, a high-level class to handle persistent configuration data (the factory) and enough glue code to connect it to the network.</p>
+
+<h2>How To Use Twisted to Be Rude</h2>
+
+<p>The simplest event to respond to is the connection event. It is the first event a connection receives. We will use this opportunity to slam the door shut -- anyone who connects to us will be disconnected immediately.</p>
+
+<pre class="py-listing">
+class FingerProtocol(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.loseConnection()
+</pre>
+
+<p>The <code>transport</code> attribute is the protocol's link to the other side. It uses it to send data, to disconnect, to access meta-data about the connection and so on. Seperating the transport from the protocol enables easier work with other kinds of connections (unix domain sockets, SSL, files and even pre-written for strings, for testing purposes). It conforms to the <code>ITransport</code> interface, which is defined in <code>twisted.internet.interfaces</code>.</p>
+
+<h2>How To Use Twisted To Be Rude (In a Smart Way)</h2>
+
+<p>The previous version closed the connection as soon as the client connected, not even appearing as though it was a problem with the input. Since finger is a line-oriented protocol, if we read a line and then terminate the connection, the client will be forever sure it was his fault.</p>
+
+<pre class="py-listing">
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.loseConnection()
+</pre>
+
+<p>We now inherit from <code>LineReceiver</code>, and not directly from <code>Protocol</code>. <code>LineReceiver</code> allows us to respond to network data line-by-line rather than as they come from the TCP driver. We finish reading the line, and only then we disconnect the client. Important note for people used to various <code>fgets</code>, <code>fin.readline()</code> or Perl's <code>&lt;&gt;</code> operator: the line does <em>not</em> end in a newline, and so an empty line is <em>not</em> an indicator of end-of-file, but rather an indication of an empty line. End-of-file, in network context, is known as <q>closed connection</q> and is signaled by another event altogether (namely, <code>connectionLost</code>.</p>
+
+<h2>How To Use Twisted to Output Errors</h2>
+
+<p>The limit case of a useful finger server is a one with no users. This server will always reply that such a user does not exist. It can be installed while a system is upgraded or the old finger server has a security hole.</p>
+
+<pre class="py-listing">
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write("No such user\r\n")
+ self.transport.loseConnection()
+</pre>
+
+<p>Notice how we did not have to explicitly flush, or worry about the write being successful. Twisted will not close the socket until it has written all data to it, and will buffer it internally. While there are ways for interacting with the buffering mechanism (for example, when sending large amounts of data), for simple protocols this proves to be convenient.</p>
+
+<h2>How to Use Twisted to Do Useful Things</h2>
+
+<p>Note how we remarked earlier that <em>protocol logic</em> belongs in the
+protocol class. This is necessary and sufficient -- we do not want non-protocol
+logic in the protocol class. User management is clearly not part of the protocol logic, and so should not be in the protocol. This is exactly why we have the factory in the first place. The factory allows us to delegate non-protocol logic
+to a seperate class. It is often not completely trivial what does and does not belong in the factory, of course.</p>
+
+<pre class="py-listing">
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write(self.factory.getUser(user)+"\r\n")
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def getUser(self, user):
+ return "No such user"
+</pre>
+
+<p>Notice how we did not change the observable behaviour, but we did make the factory know about which users exist and do not exist. With this kind of setup, we will not need to modify our protocol class when we change user management schemes...hopefully.</p>
+
+<h2>Using Twisted to Do Useful Things (For Real)</h2>
+
+<p>The last heading did not live up to its name -- the server kept spouting off that it did not know who we are talking about, they never lived here and could we please go away. It did, however, prepare the way for doing actually useful things which we do here.</p>
+
+<pre class="py-listing">
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs):
+ self.users = kwargs
+ def getUser(self, user):
+ return self.users.get(user, "No such user")
+reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
+</pre>
+
+<p>This server actually has valid use cases. With such code, we could easily disseminate office/phone/real name information across an organization, if people had finger clients.</p>
+
+<h2>Using Twisted to Do Useful Things, Correctly</h2>
+
+<p>The version above works just fine. However, the interface between the protocol class and its factory is synchronous. This might be a problem. After all, <code>lineReceived</code> is an event, and should be handled quickly. If the user's status needs to be fetched by a slow process, this is impossible to achieve using the current interface. Following our method earlier, we modify this API glitch without changing anything in the outward-facing behaviour.</p>
+
+
+<pre class="py-listing">
+from twisted.internet import defer
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = defer.maybeDeferred(self.factory.getUser, user)
+ def e(_):
+ return "Internal error in server"
+ d.addErrback(e)
+ def _(m):
+ self.transport.write(m+"\r\n")
+ self.transport.loseConnection()
+ d.addCallback(_)
+</pre>
+
+<p>The value of using <code>maybeDeferred</code> is that it seamlessly
+works with the old factory too. If we would allow changing the factory,
+we could make the code a little cleaner, as the following example shows.</p>
+
+<pre class="py-listing">
+from twisted.internet import defer
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser( user)
+ def e(_):
+ return "Internal error in server"
+ d.addErrback(e)
+ def _(m):
+ self.transport.write(m+"\r\n")
+ self.transport.loseConnection()
+ d.addCallback(_)
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs):
+ self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+</pre>
+
+<p>Note how this example had to change the factory too. <code>defer.succeed</code> is a way to returning a deferred results which is already triggered successfully. It is useful in exactly these kinds of cases: an API had to be asynchronous to support other use-cases, but in a simple enough use-case, the result is availble immediately.</p>
+
+<p>Deferreds are abstractions of callbacks. In this instance, the deferred
+had a value immediately, so the callback was called as soon as it was
+added. We will soon show an example where it will not be available immediately.
+The errback is called if there are problems, and is equivalent to exception handling. If it returns a value, the exception is considered handled, and further callbacks will be called with its return value.</p>
+
+<h2>Using Twisted to Do The Standard Thing</h2>
+
+<p>The standard finger daemon is equivalent to running the <code>finger</code>
+command on the remote machine. Twisted can treat processes as event sources too, and enables high-level abstractions to allow us to get process output easily.</p>
+
+<pre class="py-listing">
+from twisted.internet import utils
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def getUser(self, user):
+ return utils.getProcessOutput("finger", [user])
+</pre>
+
+<p>The real value of using deferreds in Twisted is shown here in full. Because there is a standard way to abstract callbacks, especially a way that does not require sending down the call-sequence a callback, all functions in Twisted itself whose result might take a long time return a deferred. This enables us in many cases to return the value that a function returns, without caring that it is deferred at all.</p>
+
+<p>If the command exits with an error code, or sends data to stderr, the
+errback will be triggered and the user will be faced with a half-way useful
+error message. Since we did not whitewash the argument at all, it is quite
+likely that this contains a security hole. This is, of course, another
+standard feature of finger daemons...</p>
+
+<p>However, it is easy to whitewash the output. Suppose, for example, we do not want the explicit name <q>Microsoft</q> in the output, because of the risk of offending religious feelings. It is easy to change the deferred into one which is completely safe.</p>
+
+<pre class="py-listing">
+from twisted.internet import utils
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def getUser(self, user):
+ d = utils.getProcessOutput("finger", [user])
+ def _(s):
+ return s.replace('Microsoft', 'It which cannot be named')
+ d.addCallback(_)
+ return d
+</pre>
+
+<p>The good news is that the protocol class will need to change no more,
+up until the end of the talk. That class abstracts the protocol well
+enough that we only have to modify factories when we need to support
+other user-management schemes.</p>
+
+<h2>Use The Correct Port</h2>
+
+<p>So far we used port 1097, because with UNIX low ports can only be bound by root. Certainly we do not want to run the whole finger server as root. The usual solution would be to use privilege shedding: something like <code>reactor.listenTCP</code>, followed by appropriate <code>os.setuid</code> and then <code>reactor.run</code>. This kind of code, however, brings the option of making subtle bugs in the exact place they are most harmful. Fortunately, Twisted can help us do privilege shedding in an easy, portable and safe manner.</p>
+
+<p>For that, we will not write <code>.py</code> main programs which run the application. Rather, we will write <code>.tac</code> (Twisted Application Configuration) files which contain the configuration. While Twisted supports several configuration formats, the easiest one to edit by hand, and the most popular one is...Python. A <code>.tac</code> is just a plain Python file which defines a variable named <code>application</code>. That variable should subscribe to various interfaces, and the usual way is to instantiate <code>twisted.service.Application</code>. Note that unlike many popular frameworks, in Twisted it is not recommended to <em>inherit</em> from <code>Application</code>.</p>
+
+<pre class="py-listing">
+from twisted.application import service
+application = service.Application('finger', uid=1, gid=1)
+factory = FingerFactory(moshez='Happy and well')
+internet.TCPServer(79, factory).setServiceParent(application)
+</pre>
+
+<p>This is a minimalist <code>.tac</code> file. The application class itelf is resopnsible for the uid/gid, and various services we configure as its children are responsible for specific tasks. The service tree really is a tree, by the way...</p>
+
+<h2>Running TAC Files</h2>
+
+<p>TAC files are run with <code>twistd</code> (TWISTed Daemonizer). It supports various options, but the usual testing way is:</p>
+
+<pre class="shell">
+root% twistd -noy finger.tac
+</pre>
+
+<p>With long options:</p>
+
+<pre class="shell">
+root% twistd --nodaemon --no_save --python finger.tac
+</pre>
+
+<p>Stopping <code>twistd</code> from daemonizing is convenient because then it is possible to kill it with CTRL-C. Stopping it from saving program state is good because recovering from saved states is uncommon and problematic and it leaves too many <code>-shutdown.tap</code> files around. <code>--python finger.tac</code> lets <code>twistd</code> know what type of configuration to read from which file. Other options include <code>--file .tap</code> (a pickle), <code>--xml .tax</code> (an XML configuration format) and <code>--source .tas</code> (a specialized Python-source format which is more regular, more verbose and hard to edit).</p>
+
+<h2>Integrating Several Services</h2>
+
+<p>Before we can integrate several services, we need to write another service. The service we will implement here will allow users to change their status on the finger server. We will not implement any access control. First, the protocol class:</p>
+
+<pre class="py-listing">
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self):
+ self.lines = []
+ def lineReceived(self, line):
+ self.lines.append(line)
+ def connectionLost(self, reason):
+ self.factory.setUser(self.line[0], self.line[1])
+</pre>
+
+<p>And then, the factory:</p>
+
+<pre class="py-listing">
+class FingerSetterFactory(protocol.ServerFactory):
+ protocol = FingerSetterProtocol
+ def __init__(self, fingerFactory):
+ self.fingerFactory = fingerFactory
+ def setUser(self, user, status):
+ self.fingerFactory.users[user] = status
+</pre>
+
+<p>And finally, the <code>.tac</code>:</p>
+
+<pre class="py-listing">
+ff = FingerFactory(moshez="Happy and well")
+fsf = FingerSetterFactory(ff)
+application = service.Application('finger', uid=1, gid=1)
+internet.TCPServer(79,ff).setServiceParent(application)
+internet.TCPServer(1079,fsf,interface='127.0.0.1').setServiceParent(application)
+</pre>
+
+<p>Now users can use programs like <code>telnet</code> or <code>nc</code> to change their status, or maybe even write specialized programs to set their options:</p>
+
+<pre class="py-listing">
+import socket
+s = socket.socket()
+s.connect(('localhost', 1097))
+s.send('%s\r\n%s\r\n' % (sys.argv[1], sys.argv[2]))
+</pre>
+
+<p>(Later, we will learn on how to write network clients with Twisted, which fix the bugs in this example.)</p>
+
+<p>Note how, as a naive version of access control, we bound the setter service to the local machine, not to the default interface (<code>0.0.0.0</code). Thus, only users with shell access to the machine will be able to change settings. It is possible to do more access control, such as listening on UNIX domain sockets and accessing various unportable APIs to query users. There will be no examples of such techniques in this talk, however.</p>
+
+<h2>Integrating Several Services: The Smart Way</h2>
+
+<p>The last example exposed a historical asymmetry. Because the finger setter was developed later, it poked into the finger factory in an unseemly manner. Note that now, we will only be changing factories and configuration -- the protocol classes, apparently, are perfect.</p>
+
+<pre class="py-listing">
+class FingerService(service.Service):
+ def __init__(self, **kwargs):
+ self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+ def getFingerSetterFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.setUser = FingerSetterProtocol, self.users.__setitem__
+ return f
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService(moshez='Happy and well')
+ff = f.getFingerFactory()
+fsf = f.getFingerSetterFactory()
+internet.TCPServer(79,ff).setServiceParent(application)
+internet.TCPServer(1079,fsf).setServiceParent(application)
+</pre>
+
+<p>Note how it is perfectly fine to use <code>ServerFactory</code> rather than subclassing it, as long as we explicitly set the <code>protocol</code> attribute -- and anything that the protocols use. This is common in the case where the factory only glues together the protocol and the configuration, rather than actually serving as the repository for the configuration information.</p>
+
+<h2>Periodic Tasks</h2>
+
+<p>In this example, we periodicially read a global configuration file to decide which users do what. First, the code.</p>
+
+<pre class="py-listing">
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.update()
+ def update(self):
+ self.users = {}
+ for line in file(self.filename):
+ user, status = line[:-1].split(':', 1)
+ self.users[user] = status
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+</pre>
+
+<p>The TAC file:</p>
+
+<pre class="py-listing">
+application = service.Application('finger', uid=1, gid=1)
+finger = FingerService('/etc/users')
+server = internet.TCPServer(79, f.getFingerFactory())
+periodic = internet.TimerService(30, f.update)
+finger.setServiceParent(application)
+server.setServiceParent(application)
+periodic.setServiceParent(application)
+</pre>
+
+<p>Note how the actual periodic refreshing is a feature of the configuration, not the code. This is useful in the case we want to have other timers control refreshing, or perhaps even only refresh explicitly as depending on user action (another protocol, perhaps?).</p>
+
+<h2>Writing Clients: A Finger Proxy</h2>
+
+<p>It could be the case that our finger server needs to query another finger server, perhaps because of strange network configuration or maybe we just want to mask some users. Here is an example for a finger client, and a use case as a finger proxy. Note that in this example, we do not need custom services and so we do not develop them.</p>
+
+<pre class="py-listing">
+from twisted.internet import protocol, defer, reactor
+class FingerClient(protocol.Protocol):
+ buffer = ''
+ def connectionMade(self):
+ self.transport.write(self.factory.user+'\r\n')
+ def dataReceived(self, data):
+ self.buffer += data
+ def connectionLost(self, reason):
+ self.factory.gotResult(self.buffer)
+
+class FingerClientFactory(protocol.ClientFactory):
+ protocol = FingerClient
+ def __init__(self, user):
+ self.user = user
+ self.result = defer.Deferred()
+ def gotResult(self, result):
+ self.result.callback(result)
+ def clientConnectionFailed(self, _, reason):
+ self.result.errback(reason)
+
+def query(host, port, user):
+ f = FingerClientFactory(user)
+ reactor.connectTCP(host, port, f)
+ return f.result
+
+class FingerProxyServer(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, host, port=79):
+ self.host, self.port = host, port
+ def getUser(self, user):
+ return query(self.host, self.port, user)
+</pre>
+
+<p>With a TAC that looks like:</p>
+
+<pre class="py-listing">
+application = service.Application('finger', uid=1, gid=1)
+server = internet.TCPServer(79, FingerProxyFactory('internal.ibm.com'))
+server.setServiceParent(application)
+</pre>
+
+<h2>What I Did Not Cover</h2>
+
+<p>Twisted is large. Really large. Really really large. I could not hope to cover it all in thrice the time. What didn't I cover?</p>
+
+<ul>
+<li>Integration with GUI toolkits.</li>
+<li>Nevow, a web-framework.</li>
+<li>Twisted's internal remote call protocol, Perspective Broker.</li>
+<li>Trial, a unit testing framework optimized for testing Twisted-based
+ code.</li>
+<li>cred, the user management framework.</li>
+<li>Advanced deferred usage.</li>
+<li>Threads abstraction.</li>
+<li>Consumers/providers</li>
+</ul>
+
+<p>There is good documentation on the Twisted website, among which the tutorial which was based on an old HAIFUX talk and was, in turn, the basis for this talk, and specific HOWTOs for doing many useful and fun things.</p>
+
+<h2>Notes on Non-Blocking</h2>
+
+<p>In UNIX non-blocking has a very specific meaning -- some operations might block, others won't. Unfortunately, this meaning is almost completely useless in real life. Reading from a socket connected to a responsive server on a UNIX domain socket is blocking, while reading a megabyte string from a busy hard-drive is not. A more useful meaning for actual decisions while writing non-blocking code is <q>takes more than 0.05 seconds on my target platform</q>. With this kind of handlers, typical network usage will allow for the magical <q>one million hits a day</q> website, or a GUI application which appears to a human being as infinitely responsive. Various techniques, not limited but including threads, can be used to modify code to be responsive at those levels.</li>
+
+<h2>Conclusion</h2>
+
+<p>Twisted supports high-level abstractions for almost all levels of writing network code. Moreover, when using Twisted correctly it is possible to add more absractions, so that actual network applications do not have to fiddle with low-level protocol details. Developing network applications using Twisted and Python can lead to quick prototypes which can then be either optimized or rewritten on other platforms -- and often can just serve as-is.</p>
+
+</body></html>
diff --git a/doc/historic/FirstTenYears.Quotes b/doc/historic/FirstTenYears.Quotes
new file mode 100644
index 0000000..1c54867
--- /dev/null
+++ b/doc/historic/FirstTenYears.Quotes
@@ -0,0 +1,5816 @@
+December 5, 2000:
+Washort says, " $self._hasIntelligence()"
+Washort says, "1"
+Washort says, "*ponders setting that to 0 on certain people*"
+Maxwell says, "yes, that's our Ego-Enhancing API"
+ [this from before we had 'emote'. I added it 10 minutes later. -ed]
+%
+You say, "I wanted to discourage people from using the [old] code as examples...".
+You say, "but I don't think that bad java style is going to damage your budding programming skills :)".
+* washort nods. 'I seriously hope not.' [see, i told you I added 'emote' -ed]
+Washort says, "Oh, did i ever tell you about the Java assignment i had at the beginning of the semester?"
+Washort says, "I was bored so i did it without any loops or temp variables."
+Washort says, "so... don't tell ME about bad style. ;)"
+%
+<glyph> it's times like this when I wish I could just swallow my pride and use a standard thing like asyncore :)
+%
+<jedin> Since it's completely unsolicited, I'd just like to add that anyone who tries implementing Keynesian economics in this game will be put behind the door with the Elder Sign....
+%
+<Nafai> I love Python. It has made me look smart in this consulting job. Because of how easily I was able to do what they need me to do, they ended up doubling my pay rate. :) Woot!
+<glyph> Woot *indeed*, good sir. :)
+%
+<glyph> you know, when I say *now* I mean "in a minute" :)
+%
+Glyph: "You need to start working on Twisted Reality."
+Mike: "What makes you say that?"
+Glyph: "Because it pains me to hear you talk about how you were 'in the same bed as' someone on AIM. There is no bed. There is no spoon. There's just some gay-ass peer-to-peer shit going on."
+%
+02:26:44 AreteComp: I've decided I'm going to warn you every 5 minutes until you go to bed.
+(You have been warned by AreteComp (5%))
+02:28:48 AreteComp: Tick, tick, motherfucker.
+%
+<washort> "TONIGHT on CELEBRITY DEATHMATCH: Kenaan vs The Shrike"
+%
+<washort> yow. autoconf can be *thorough* sometimes..
+<washort> "checking for EBCDIC... no"
+<washort> i hesitate to ask what it would have done had the answer been "yes"
+%
+<glyphAtWork> the http server was so we could say "Web!" if we ever did a freshmeat announcement
+<glyphAtWork> this makes people excited
+%
+FifthKow: jello is beyond good and evil
+%
+<washort> besides, we need a way to handle the cases of characters on drugs...
+%
+<jerji> sorry glyph, but I have to take away your dork award. det is far more deserving.:)
+%
+<det> glyph: you be on tomorrow ?
+<glyph> det: what, you think I'll suddenly grow a life?
+%
+<glyph> det: if it were any more generic it would be socket.socket
+-- (responding to det's request to make twisted.web more generic)
+%
+<glyph> GenericBoy: Dude, this is *python*... objects get created when you sneeze
+%
+<tenth> I get the feeling that I could rack up some ad impressions by posting an announcement to FM about a webserver "powered entirely by love, that I made out of this bong I had".
+<tenth> Well, as long as it did something really l88t that other bong-servers didn't do, anyway.
+%
+01:35:08 AreteComp:
+Before you finish linking, you must answer the following:
+Are you a Jew?[y/N]: y
+Nice try, Yid.
+%
+<samuel> oh why do you mock me rpm
+%
+<jerji> oh no!
+%
+<tpck> http://www.twistedmatrix.com/whatisdivunal.html << makes it sound likes its done and played by millions worldwide
+<washort> tpck: that's what ad copy is for
+%
+<tpck> glyph: I thought Enterprise Class Software wasn't supposed to crash?
+<glyph> tpck: It costs extra for the kind that doesn't crash, I think
+%
+<glyph> now you're probably wondering how to run cvs
+<samuel> actually i was thinking of naked women.
+<samuel> but sure.
+%
+<\\mimic> graydon: it's when you start constructing conditional branches in sed that the men in white coats come for you
+<graydon> mimic: been there, done that. wrote a qmail crypto extension in sed this summer :)
+%
+<jerji> dude tf programming, in my experience, was just about reading the help file and hacking something until it worked.
+<jerji> not really the kind of place to employ software engineering principles. ;)
+%
+<denial> CanDoo: I would rather run a home trepaning centre than do tech support :)
+%
+<zedboy> washort "A given program in PERL is like a turd. you can see it. smell it. touch it. yuck! it's definitely a turd. it's compact. it's smelly. it's brown. a turd, thru and thru"
+<zedboy> washort "The *same* program in C/C++/Java/your favorite imperative language here is like a roll
+ of toilet paper, with the turd smeared *all over it*. you tear of one sheet. yuck! another sheet. ugh! another sheet. ewww! etc"
+ --- quoting Chet Murphy
+%
+<shapr> I get the feeling regexes in emacs are subtly different from python
+%
+<moshez> I'm not touching anything not abstracted from hardware at least two levels
+%
+<dreid> wh00t!
+<dreid> i made the quotefile!
+%
+<dreid> "lispachu, parentheses attack!"
+%
+<e@ircnet> internet
+%
+<washort> this was experimentally determined using an unholy combination of emacs, python's interactive mode, and bc
+%
+<smoke:#lisp> perhaps i should write a "Teach yourself CL in 21 days" book and hide from Peter Norvig for a few years
+%
+<GenericBoy> I'm not high!
+%
+<Mike_L> what is twisted python?
+<glyph> Mike_L: it's the python libraries your mother would use, if she were a programmer, had a lot of free time, and was very VERY patient
+%
+KaraNiSuru: Your opinion has differing degrees of importance to me. On
+programming, it's almost like law; on fashion, it's unimportant; on cuteness,
+it serves only to warn me away.
+(addressed to glyph, from his girlfriend)
+%
+<washort> glyph: you're evil, too
+<glyph> washort: I try
+<washort> not the good kind of evil
+<washort> the other kind
+%
+<Yosomono@efnet> swing is to gui programming what cupholders are to cdrom drives
+<Yosomono@efnet> something easily mistaken for the real thing
+%
+<glyph> well, I'm working on divunal now
+<washort> and what are you doing to it?
+<glyph> I'm making the clouds work again
+<glyph> the clouds were always one of my favorite bits
+<washort> bah
+<washort> typical vapourware
+%
+<glyph> washort: I learned C from reading the E sources.
+<washort> glyph: well, i learned python from reading Zope
+<washort> glyph: so i think we're about equally damaged
+%
+--> glyph (glyph@adsl-64-123-27-108.dsl.austtx.swbell.net) has joined #python
+<glyph> yay for pushing the wrong button
+<washort> when will you xchat people learn
+<washort> silly hacker, irc is for terminals
+<washort> you dont see *me* typing '/quite' by accident ;)
+<-- washort has quit (either =))
+--> washort (washort@131.204.216.12) has joined #Python
+<washort> glyph: you bastard.
+%
+<GenericBoy> I wish I had enough knowledge to start working on this damn thing
+<GenericBoy> glyph: But you had to crush my hopes. ;)
+<GenericBoy> not that that's bad though, I am grateful for giving me a better
+perspective
+<glyph> GenericBoy: crushing hopes is what I do best
+<glyph> GenericBoy: you call me "glyph", but in ancient mesopotamia they called me the "eater of souls"
+%
+<glyph> many as-yet-untranslated pre-cuneform tablets will one day be
+translated to say "beware he who will write a webserver that will deprive you
+of your very will to live!"
+<glyph> GenericBoy: although I'm not sure if they were talking about me or
+marc andreissen
+%
+<GenericBoy> I'll be the t.w guy from now on
+<glyph> yay!
+<glyph> YAY!
+<glyph> SOMEBODY ELSE IS GOING TO MAINTAIN MY SOFTWARE
+<glyph> oh god I think I'm going to cry
+<GenericBoy> ack
+<washort> GenericBoy: i think that was a mistake :)
+* GenericBoy runs
+%
+<glyph> GenericBoy: * New in 0.8.0: carmstro's soul now comes with twisted.web
+%
+<jepler> C:
+<jepler> char buf[1024]
+<jepler> strcpy(buf, user_data)
+<jepler> Python:
+<jepler> buf = user_data[:1024]
+<jepler> if len(user_data) > 1024: security_hole(user_data[1024:])
+<jepler> actually, the translation is not difficult, so long as you implement security_hole() properly.
+%
+<LynchM0b> ... do u have an easier way
+<glyph> python ;-)
+<LynchM0b> thank the lord
+<LynchM0b> java is rediculous
+%
+<yy[Z]@efnet> i can say with all confidence that my python code was the
+fastest and tightest code on the whole java project i been on for the last year
+%
+<ben3> OO is a seductive failure.
+%
+<dreid> washort: i don't want to take over the world
+<dreid> i want to marry the chick who is going to take over the world
+%
+<demoncrat> forth is much better than sanity
+%
+<snibril> Tim can go on at length on issues which are not really the core of the problem, complicating said problem for himself and everybody else.
+<snibril> glyph: sounds like you ;)
+<glyph> snibril: the difference is there is rarely actually a problem, when I'm involved :)
+%
+<glyph> So if I understand you correctly you want software that will b-2-b education portal internet enterprise mission-critical!
+<muks> yep
+<glyph> Ah. then you want Zope.
+%
+<shapr> glyph: ok, where's the tw tutorial?
+<glyph> shapr: feh, you think there is *documentation*? You just need to be at harmony with the universe, and the api calls will come to you.
+<shapr> I just got a job writing Java. harmony is nowhere close to me.
+%
+<shapr> I just *love* your Python vs Java rant :) it's GREAT
+%
+<shapr> glyph: while reading through the last part of your rant, I got this mental picture of "Glyph Lefkowitz, Python Ninja" systematically chopping limbs off the JVM
+<shapr> glyph: the problem is that "don't expect your apps to run" was cutting the head off, and for cinematic effect, it should be on the bottom
+%
+<dreid> GenericBoy: but multiple eterms tailing various logfiles are great for making it look like your actually doing something :)
+<GenericBoy> hehe
+<dreid> i'm preparing myself for when i have to work in a corporate setting
+%
+<TomG> I'm in the wrong channel.
+%
+<dreid> Yoso: i like to think that i'm a fairly sane individual for a python programmer anyway
+%
+<glyph> snibril: I think we should put *you* in the unit tests dir.
+%
+<bram> have I mentioned there's a FRIGGIN BUTTLOAD of ways web input can go bad?
+%
+<washort> GenericBoy: if we knew what we were doing, we would not call it programming
+%
+<GenericBoy> Usually relying on magic buttons from the future doesn't work
+[in reply to something Mike_L said. --ed]
+%
+<blupingu> hi glyph. i'm trying out python because of twisted python :)
+%
+ newpath = os.path.join(self.path, path)
+ # forgive me, oh lord, for I know not what I do
+ p, ext = os.path.splitext(newpath)
+%
+<Nafai> There's a twisted python philosophy tutorial?
+<washort> Nafai: yes.... read it, expand your consciousness
+<obanta> It's actually a new religion
+%
+<glyph> washort: coding angry lends whole new meaning to song lyrics :)
+%
+<GenericBoy> what's the point of all of this?
+<glyph> GenericBoy: I don't know
+%
+<Acapnotic> [ Read Past Entries ] [ Modify an entry ] [ Write new entry ] [ Have me add one for you. ]
+<Acapnotic> include: [ ] generic angst [ ] relationship trouble (or lack thereof)
+ [ ] other family trouble [ ] cynical technology rants
+<Acapnotic> also bash: [ ] slashdot [ ] users [ ] sysadmins [ ] politicians [ ] voters
+<Cysgod> [ ] Perl
+
+ -- proposed new configuration interface for the standard twisted.web weblog
+%
+<moshez@ircnet> I'm going to write a treatise "girls as open-source projects".
+<moshez@ircnet> Instead of "reaching second-base", you're "writing patches".
+<moshez@ircnet> "So, are you writing patches for you-know-who?" "Well, no, but I'm using the CVS version"
+<moshez@ircnet> Should translate to "We're only kissing, but that's as serious as it got"
+%
+<dreid> watching a beautiful girl sleep is amazingly fun
+<dreid> more fun than coding
+<washort> do you mean 'more fun than coding Enterprise Applications in java', 'more fun than coding display hacks in C', or 'more fun than coding weblogs in python'?
+%
+ # ha ha, python can do lexical closures good enough for me
+ # (Bah. if these were lexical closures you wouldn't need the
+ # 'obj=obj', and you could do 'return setdesc' and the
+ # function would still work after escaping. -was)
+ def setdesc(desc, obj=obj):
+ obj.description=desc
+%
+<_Krelin> Data hiding and encapsulation are at least in-laws, if not blood brothers
+<glyph> data hiding is encapsulation's shrewish mother-in-law
+%
+<dreid> Zope is pretty much the reason i learned PHP, (and TPy is the reason i stopped)
+%
+* Nafai doesn't think he is worthy of the quotefile
+<washort> you're in it twice
+%
+<washort@opn> "twisted python.... it's featurrific!"
+%
+<moshez> living is just syntactic sugar.
+%
+<det> glyph: what are you going to do now that UO2 is canceled ?
+<washort> det: take over the world
+<washort> det: same as before
+<det> washort: but thats what he was going to do last night
+<washort> det: glyph is a man of habit
+%
+<washort> who invented this "time zone" crap? everybody should be on IRC at once
+%
+--- washort has changed the topic to: | <-- you must be smarter than this stick to ride the internet
+%
+<Krelin> TwistedPython may, in fact, have both "enterprise" AND "internet" ;)
+%
+<thirmite@efnet> nothing like a pop tart to remind me i live in a first world country
+%
+<glyph> yosomono: in fact, I'll turn this box of Cheese Nips and
+ this 3-liter bottle of Mountain Dew into a irc2web interface
+%
+<h3x> actually i have clothes on
+<h3x> believe it or not
+%
+<\broken:#openprojects> geez that tomg bot is in here again
+<\broken:#openprojects> didn't we ban it a couple of times already
+%
+<Yosomono> uh, move zig zamboni to push grandma cats down the stairs to protect her/him from the terrible secret of space, which is that she/he can't skate?
+%
+<washort> o/` once i was the king of spain o/`
+* Acapnotic throws a humble pie at washort
+%
+<bram> the more I get into the art of design, the more I design things like I'm seven years old
+<bram> 'I don't want to do things that way because it's too hard'
+<bram> 'I wanna do it like this because I understand it'
+<bram> 'I'm ignoring that because it's scary'
+<bram> 'I don't want to work with him because he's a poopy-head'
+<bram> 'I don't want to use this because it smells like poo'
+<bram> 'this is no fun any more, I'm going home'
+%
+<cube> Greetings, O Twisted One
+%
+<Forest> Someone please tell me that this thing about P3K and Perl 6 is just a sick April Fool's joke
+<glyph> Forest: what, print>> wasn't a big enough hint?
+%
+* moshez lives to workaround design decisions made by others.
+(-- after just proposing to implement IRC over HTTP via Zope.)
+%
+ <idcmp> /msg ry a/s/l
+%
+<glyph> okay, cvs is scaring me
+<det> glyph: when I was 5, when the other children were going as ninjas and dracula, I went as CVS!
+<glyph> det: you should have gone as SCCS
+<det> glyph: you gotta be a little cute to get the candy
+%
+<cube> If you are anal, and you love to be right all the time, C++ gives you a multitude of mostly untimportant details to fret about so you can feel good about yourself for getting them "right", while missing the big picture entirely
+%
+<cube> C++ extends the machine-efficiency requirement all the way up from
+ line-by-line implementation into entity abstraction as classes, it
+ corrupts far end of the coding spectrum with "efficiency" concerns.
+%
+<glyph> that's why I love IRC
+<glyph> you can't be late for IRC
+%
+<radix> uh oh.
+<radix> 'destroy here' isn't a good idea. :)
+%
+<radix> glyph: the problem with writing a framework for text universe is
+that text adventure authors want to do the craziest shit :)
+%
+<thirmite@efnet> are you jewish?
+<moshez> yes.
+<moshez> be afraid
+%
+<thirmite> btw, e, what are the girls like in .fi?
+<e@ircnet> bipedal, warm blooded, pink skinned, about 1.5-2.0 meters tall
+%
+<dash> jeffk isn't funny, the people who think he's real are funny. :)
+<thirmite> he isn't real?!?
+%
+<cyli> it'd be so cool. i'd feel all l33t and shit
+%
+<dash> if they had neural interfaces to computers, we'd both be dead by now
+%
+<skreech> I declare myself god
+<skreech> the end
+%
+<radix> GenericBoy is no more
+<radix> I killed him, and have taken his place
+<Acapnotic> radix: whadja do with the body?
+<det> Acapnotic: killed in a metophorical sense
+<radix> that's what you think.
+<Acapnotic> What happened to the metaphorical body?
+<det> Acapnotic: the metaphorical body is decaying at the bottom of lake washington
+<radix> that's what he thinks.
+%
+ <dnm:#lisp> i'm convinced the core of loop [the Common Lisp facility] is a n-dimensional singularity and
+ that the common macro people implement is merrely the tessaract to loop's hypercube.
+%
+<radix> I'm an at least somewhat-educated dope fiend
+%
+<jedin> I figured your lasers would be a good impetus to action.
+<glyph> Don't forget about them.
+<glyph> They're hovering, just over your head... where you can't see them. Remember that.
+<jedin> Okay.
+<jedin> Hm. That could be a cool theme for a new breakfast cereal!
+%
+<moshez> glyph: I don't know anything about reality.
+%
+<Acapnotic> There are *many* differences between Texas and yogurt. Texas is drier than yogurt. Texas is larger than any amount of yogurt I've seen in one place at a time. (or ALL the yogurt I've seen at ANY time). Eating Texas would be less enjoyable than eating yogurt.
+<Acapnotic> Texas does not come in eight ounce plastic containers with tinfoil lids. To the best of my knowledge, there is no "fruit on the bottom" version of Texas. Texas is not available in the dairy section of your grocer. Texas does not help fufill your daily dietary requirement of calcium.
+%
+<eAndroid> MAKE YOUR LOGO AL GORE ON A STICK
+%
+<dash> radix: the question is, do you _really_ want to do that? :)
+<radix> no, but I want to make other people do it
+%
+<Rainy-Day> i ascended several times.. once as a tourist without wishes or material transformations
+%
+<radix> dash: Hey, what do you think a good visual aid for a talk on anarcho-capitalism would be?
+<glyph> radix: a gun.
+<glyph> radix: correction: a gun and a big pile of money :)
+%
+<mothra> Most large software projects are disasters. Nothing new there.
+<dash> most large software projects use java or C++. not a coincidence.
+%
+<dash> the program isn't debugged until the last user is dead
+%
+<moshez> glyph: I prefer to think of it as a community project...since not every interface is equal
+<moshez> some interfaces are more equal then others.
+%
+<det> glyph: why not use xml? (only because it is sort of a python standard [dont kill me])
+%
+<LiquidAngle> can you do socket programming with python ?
+<dash> boy can you _ever_
+%
+<spiv> In python, you can, but in Java you can't.
+ [ this comment had context, but it's really just axiomatic --ed]
+%
+<faassen> I'm not a python luminary, I just play one on TV. :)
+%
+<h3x> but the point is, i dont have to juggle dlopen() bullshit
+<h3x> because that gets old real fast
+%
+<glyph> shapr-werk: I can't even imagine the hell of having to write java while quitting smoking. I am behind you 100% ;)
+<shapr-werk> glyph: yah, anyone in front of me has already been mauled :-)
+%
+<Krelin> glyph: You have created a powerful solution for which there are no problems. Everyone is impressed, but duly confused.
+%
+<radix> crack! *that's* what I need!
+%
+<glyph> I like writing code that overloads operators in python
+<e> get help
+%
+<Yosomono> rasterman is the millionth monkey
+%
+<solomon> john tesh get out of my head!
+%
+<dash> i want distributed everything
+<dash> yesterday
+%
+<parks> glyph please please dont jump on the P2P XML bandwagon
+<dash> parks: satan will be buying ice skates before glyph does that
+%
+<shapr> this is where I tell you to stop hyperfocussing on bad stuff and think about something nifty like metaclasses or sex
+%
+* itamar loves changing an object's own class at run time
+<snibril> itamar: and you eat little babies, too
+%
+<TQuid> So glyph is a master of the occult as well as the obscure. :)
+%
+<glyph> "What?" "Take the red continuation." "What?" "Take the blue continuation." "Huh?" "Take the red continuation." "What?"...
+%
+* rik cheers for twisted python
+<rik> it's the easiest network coding toolkit I've come across
+<rik> as soon as you have the flash of inspiration as to how it works, you'll not look back
+%
+<dnm> ugh. linear cosmologist fever.
+%
+<e> we have powers that reach beyond the pickle
+%
+<TQuid> "No one expects the python acquisition!"
+%
+<laotse> I'm sorry. I used to be sane. Then I learned Perl and now I'm like this. ;)
+<dash> laotse: that's my excuse too
+<dash> laotse: that, and 4 years of university CS
+%
+ <h3x> i get my best programming done in the nude
+%
+<timmy> what is the recommended way to do client sockets in python
+<e2d2@ircnet> timmy: a chainsaw! AHAHAHAHA!
+* e2d2@ircnet goes back to sleep
+%
+<Acapnotic> No more doc about twist-dee, needs another page or three...
+<Acapnotic> You call this an application server? This is a slide projector and a bedsheet!
+* Acapnotic is going to have to speak to Bob about this.
+%
+<phed> dash: that's the cool part of system programming, programming half-finished programs, and tell others you're finished
+%
+<dash> if i'm going to use an obscure language with poor system integration, it might as well be lisp
+%
+<radix> I feel so special when people quote me
+%
+<spiv> dash: so we need to wrap integers... Java does that too, so it can't be that bad ;)
+* dash doesn't know how to respond to that except with physical violence
+%
+<glyph> It's just like a method call, but ON FIRE AND UPSIDE DOWN!!!
+%
+<dash> design patterns in general are just java/c++ crutches
+<dash> which isn't to say they're useless. when your language is crippled you need crutches
+%
+<laotse> Java is the tell me when I've been bad language ;)
+%
+<e> so he is writing a python interpreter in python
+<firegod> dash: is he actually that evil?
+<dash> firegod: for glyph, this is relatively non-evil.
+%
+<gary> btw, my gcc compile line is gcc -o foo foo.o includes.o -lstdcxx is there I can cut that stdcxx out? My executables are like half a meg.
+<glyph> nope
+<glyph> if you didn't want your executables to be huge and slow for no reason, you could stop using c++ :)
+%
+<glyph> the industry average per programmer/day is 10 lines
+<gary> yeah I know. its sorta sad.
+<gary> have you ever wanted to, like, be part of the backstreet boys or something? It would probably make life quite easier. Or at least different.
+ [This is what C++ does to your brain, kids. -ed]
+%
+<red_one> PORK IS NOT A VERB.
+%
+* shapr reads market speak
+<shapr> Vertical navigation through business domain trees (classification trees).
+<shapr> Horizontal navigation through multidimensional classification trees.
+<shapr> I bet moshe wrote this advertising
+%
+<snibril> glyph: others do secret sex perversities, and you join #c++
+%
+<jumpy> we are the knights who say INT! SHORT! and UNSIGGGGGGGGGGGGGGGGNNNNNNNNNNNEDDDDDDDDDDDDD LONNNNNNNNNGGGGGG!
+%
+<saint_go@efnet> Why?
+<dash> because C++ is an excellent language for doing slow and late projects in. :)
+<makk@efnet> dash: at least it's good for something. :)
+%
+KaraNiSuru: who needs a real live girl when you can get thousands of prettier girls displayed on a gorgeous 1365x768 resolution, 16.7 million color flat-panel plasma tv?
+%
+<radix> yeah, I saw that OBSOLETE_base attribute and thought to myself "Maybe glyph already tried this, and found that it sucks"
+%
+<jafo> Our fathers were our models for God. If they bailed, what does that tell you about God? You have to be prepared for the possibility that God does not like you.
+%
+<Acapnotic> ... whenever I hear anything in this channel that smacks my brain three feet into kata, chances are that glyph is the one that said it
+%
+<hunter> ... I'm execfile()'ing a file provided by j random sysadmin, so I'm pretty much holding a gun to my head.
+%
+<deeptape> I just got a vision of a version of Gaunlet that pits Pythonistas against an endless horde of C++ and Java zealouts
+<deeptape> Red Hacker needs Source, Badly
+%
+--> java (dutkiewicz@91.portland-01-02rs.or.dial-access.att.net) has joined #python
+<java> yes
+<-- java (dutkiewicz@91.portland-01-02rs.or.dial-access.att.net) has left #python
+<glyph> goodbye, java
+<glyph> hehe, that's surprisingly satisfying to say
+%
+<glyph> dash! dash! he's our man! If he can't do it, we'll make him write in ASP until he dies! bwahahhaha
+<dash> i hate you, milkman glyph
+%
+<thirmite> i have <glyph> and <dash> both subbed to the one message: <tpy> $1-
+%
+<glyph> so thirmite
+<glyph> it sounds like you have some issues
+<thirmite> duh i've been hanging around #python for around 4 years
+<thirmite> i have every issue possible
+%
+<Acapnotic> glyph: why are you being an asshole and insisting on seven bits instead of eight, anyway?
+<glyph> Acapnotic: because I gave up that bit in exchange for eternal life
+%
+<churchr> XML wasn't invented. It was excreted.
+%
+<faassen> I'm not a PSU agent.
+%
+<thirmite> i *think* i have a girlfriend
+%
+<dash> roey: i've got some code you should look at
+<dash> roey: http://twistedmatrix.com/
+<Acapnotic> dash: it's amazing how much you can make "I've got some code you
+should look at" sound like "do you have stairs in your house?"
+%
+<Mike_L> hmm ELF sounds complex =/
+* Mike_L hates file formats
+<Mike_L> i suppose I could just make my bytecode file format based on XML
+%
+<glyph> I am *not* a PSU agent.
+%
+<dash> glyph: nice people dont name functions "b1282int"
+%
+<jemfinch> I mean, if GNU wants everything to use guile, they should probably make it suck less.
+%
+<dash> i feel the power of the confusatron
+%
+<Yosomono> [Ying] is a fantastic artist, that's for certain.
+%
+<shapr> so, is the twisted crowd moving to Oz?
+<dash> shapr: no. Oz is coming to _us_
+%
+<h3x> why dosent someone write a rfc or w3 spec on server push text fields?
+<Acapnotic> look, everybody knows that "push" had it's chance, and it flunked. Pushing failed. Pushing is not the answer.
+<dash> Acapnotic: SHOVING IS THE ANSWER
+<Acapnotic> yes, shoving is the answer. We must have shoving streaming media. "I am the shover transport -- I push the newsfeed down their throats."
+%
+<mitiege> dash: where do you go to school?
+<tpck> mitiege: PSU
+<mitiege> tpck: didn't faassen go there too?
+%
+<eihrul> .NET is kinda the quickening
+%
+<shapr> we're all CODEpendant.
+%
+<robbe@ircnet> AttributeError: CMD
+<robbe@ircnet> what is here the failure?!?
+<radix> robbe: the 'cmd' object doesn't have a member named CMD
+<robbe@ircnet> radix: how i can make it?
+<radix> robbe: set us up the bomb
+%
+<M-x> sure, excessive use of the Emacs causes social problems
+<M-x> in understanding the trivial problems other people have
+<M-x> like you see them indenting a whole file of source code manually, or jumping between make output and trying to find the offending line
+%
+<dash> "COM Error: Errors occurred"
+* dash attacks ASP with a rusty hacksaw blade
+%
+<lyn:#lisp> making things fast generally seems to involve trading space for time
+<dan`b:#lisp> not so! you're thinking like a typical lisp programer
+<dan`b:#lisp> you can also trade correctness, like any self-respecting C hacker
+%
+<shapr> dash: I know Python adds sanity points to me.
+<dash> shapr: reading glyph's code does not
+%
+<tireg> i see the light!!
+<tireg> AND IT BURNS!
+<dash> tireg: welcome to python
+%
+<Acapnotic> jemfinch: What's to parse? A numeric code, perhaps a chicken, and some arguments
+%
+<Acapnotic> dash: yes, about that, do you have anything besides spam?
+<dash> Acapnotic: got spam, spam, internet, enterprise, and spam
+<dash> Acapnotic: that doesn't got _much_ spam
+%
+<itamar> if moshez ever gets into the Python RPG he'll have "different definitions of basic concepts leads to conflicts with everyone" as a disadvantage
+%
+<e@ircnet> meikan adsl:n asennus makso sentaan muistaakseni 3000 ja silta
+sedalta meni 10min :)
+<e@ircnet> oops, wrong channel
+<radix> eek
+<radix> scary words
+<dash> radix: ph34r the ph1nn1sh!
+%
+<matsaleh> glyph has been *trying* to bring me up to speed on twisted
+<matsaleh> all I know is that if he gets any smarter i'm in trouble
+<dash> matsaleh: we already are, i think
+%
+<moshez> glyph: what's Twisted Matrix Laboratories?
+<dash> moshez: the only enemy the PSU fears
+%
+<radix> scripts are just usually short programs that do a very specific thing
+<radix> that's why a lot of us people who use interpreted languages hate it when someone calls our language a "Scripting language" ;)
+<radix> (I mean, look at Twisted and call it a "script" with a straight face)
+%
+<Afterglow> glyth: what's odd is i keep getting a segfault and i don't know why
+<glyph> Afterglow: are you using C?
+<Afterglow> yes
+<glyph> Afterglow: ah. There's your problem.
+%
+<peryklez> should i learn python?
+<moshez> peryklez: no. instead, you should be an anarcho-vegeterian.
+<moshez> peryklez: here, see this channel? Do you ever see us talking about Python? No! Because Python sucks.
+<glyph> peryklez: Yes. You should learn python.
+<glyph> peryklez: Also, stay away from crack cocaine, which moshez is evidently smoking...
+%
+<radix> well, running it works well :>
+<glyph> radix: yeah, but don't ask what it does because it'll KILL YOU WITH
+ITS TEETH
+%
+<dnm_> Twisted tickles my high-level competent software design and concisely functional code that does something useful which was done poorly elsewhere in comparison bones.
+%
+<snibril> radix: i met some _professional_ (or supposed-to-be) admins that had probs even with "ldd"
+<dash> snibril: so, uh.... what did these guys _do_?
+<snibril> dash: ask stupid qs
+%
+<e@ircnet> on the internet the concepts of time and space lose meaning
+%
+<matsaleh> well, maybe we should evangelize a bit...
+<matsaleh> one thing to do would be to convince some kind of public site - techie oriented - to use twisted in some implementation
+<glyph> any ideas come to mind?
+<matsaleh> start small... google? :)
+%
+<radix> I was drinking tea before this job
+%
+<LcModerator:#live> <radix> have you heard of Twisted? Did you know that TwistedMatrix Laboratories is the only feared enemy of the PSU?
+<gvanrossum:#live> radix: I've heard of Twisted and even downloaded his code once, but I couldn't understand one bit of it. Twisted, if you're here, sorry, but that's a fact.
+<gvanrossum:#live> The PSU, of course, doesn't exist.
+<dash:#python> radix: you're a bad, bad boy
+%
+<snibril> guido, when will you stop calling python a scripting language? ;)
+%
+<gvanrossum:#live> zilch: I'm a big fan of wxPython [...]
+<radix:#python> I no longer respect that man
+%
+<gvanrossum:#live> What afro?
+%
+<dash> jenn: you DONT FEEL LIKE PROGRAMMING? what's WRONG with you??
+%
+<Erwin> #python FAQ: How do I build X? A: Wait for twisted.X.
+%
+<thirmite> i'm in the psu!
+%
+<e> most people on irc are professional and shit.
+%
+<dash> glyph: maybe that'd stop, if we stopped denying that the PSU is real and is actually coordina~~4%~~..~*'#n`+>~~.]
+<-- dash has quit
+%
+<rbm> glyph: Now I want to get more to your side of the darkness >:->
+%
+* Nafai will vouch for the fact of glyph's being the master of the obscure
+%
+<churchr> glyph: So why can't you make that into a database?
+<glyph> churchr: I will set you on fire.
+%
+<skreech> I'm gonna kinda miss code red when its gone, my webpage has never gotten this many hits before
+%
+<Acapnotic> garble. if I don't find a twisted.spread example soon, I might try to figure out what .spread is supposed to do by looking at the source directly
+<Acapnotic> which would probably be unhealthy
+<dash> Acapnotic: hey! i've been reading the source for the past month! didididididn't bother me at all!
+* dash giggles
+%
+<Acapnotic> hmm. I wonder what would happen if you fed .bash_history to megahal and then set that as your shell.
+%
+<thirmite> the pull to IRC is so much less now i have my drivers license
+<dash> thirmite: so why are you telling _us_ that? ;)
+<thirmite> dash: who else am i gonna tell? :)
+%
+<thirmite> faassen: it was on the internet
+<faassen> thirmite: don't use the internet.
+<thirmite> i love the internet
+<h3x> pike, the language of your internet
+<Jii> what's internet?
+%
+<glyph> HELP ME SMALL CHILD I HAVE ATTEMPTED TO CREATE A WEB SERVER BUT I HAVE BECOME LOST
+%
+<glyph> I *hate* thinking.
+%
+<Acapnotic> Unlike BASIC, Python doesn't have circle-drawing and paint-fill operations either.
+%
+* glyph returns
+* rik wonders what glyph returns
+<radix> rik: NOT_DONE_YET
+%
+<thirmite> dash: i don't really IRC while drunk *anymore*
+%
+<_pHI_> what is twised.words? and why did i just create an account :) ?
+%
+<churchr> I don't know why you guys want to hurt people.
+<glyph> churchr: money, usually
+<dash> glyph: wow, i can get _paid_ to hurt people?
+<dash> they didn't mention this at Career Day
+%
+<glyph> ddent, the man who was born to program in python, but doesn't
+<dash> glyph: you're thinking of his evil twin, "ndent".
+<shapr> Python as Guido and ndent did.
+%
+<shapr> I am an object!
+%
+<glyph> you know, if I'm going to develop a massive cult of personality, I need to have a better website
+%
+<Inhibitor> this is commercial software - there are no security holes
+<Inhibitor> not like your crappy open source - written by students - stuff
+<glyph> right, I had forgotten
+%
+<e@ircnet> i have been known to occasionally infact say "internet".
+%
+<Yosomono@efnet> The next version of Shapr 0.96 will have integrated Twisted support.
+%
+<Yosomono@efnet> glyph: the colors! the colors! they're burning my eyes!
+%
+* shapr goes into his a capella techno rendition of "mission impossible"
+<shapr> doodle oooo.... doodlee ooo!!
+<radix> doodlee ooo??
+<radix> I don't remember that part
+<shapr> radix: yah, that's at the beginning
+<bitPoet> radix: that's before the duh-duh-duhduh-duh-duh-duhduh part
+%
+<bram> talking about the engineering of p2p apps is like talking about the engineering of red cars
+%
+<thirmite> well the only way i could think of a girl turning me into a vegetarian is by offering me continous sexual favours, but that wouldn't work on glyph because he has some sort of dignity
+%
+<glyph> no land wars in asia or sicilian blood feuds
+<glyph> or threads
+%
+<glyph> it's easy to be dogmatic when you're right and everyone else is an idiot
+%
+<e@ircnet> thirmite: we added window manager support to bridgette.
+<thirmite> e: i hope you're drunk
+%
+<e@ircnet> error handling is important, arguing tha silent failure is ok for "production systems" does not alleviate problems when something goes wrong with "production systems" :)
+%
+<h3x> everybody is left of something
+%
+<glyph> but one person's identity could have multiple perspectives
+<e@ircnet> multiple perspective disorder
+%
+<e@ircnet> glyph: that would make twisted the most buzzword compliant application server platform known to man!
+%
+<glyph> "Fetch me my internet pants."
+%
+<moshez> What is programming, if not fighting a world of idiotic design decisions?
+<moshez> And where can you find design decisions more idiotic?
+<glyph> moshez: landscaping
+<moshez> glyph: hmmm......point.
+%
+<eAndroid> Guido has been on crack for a while.
+<eAndroid> I think he bought some cheap stuff, that's all
+%
+<dash> this feels like saving christmas from santa claus
+ [on trying to prevent Guido from making python less dynamic]
+%
+<e@ircnet> fwiw writing a sexp parser in virtually any language is easier than learning to use xml libraries for that platform.
+%
+* Blackb|rd has been spoiled by years of C and C++ and the hideous exposition to Java 1.0.2
+<radix> Blackb|rd: not "spoiled", "mentally mutilated"
+%
+<glyph> CHECKED IN
+<radix> RUN!
+<glyph> GENERATE CODE!
+* dash runs around in circles screaming, then falls over
+<glyph> INTRODUCE INSTABILITY!
+<glyph> SUDDEN EXIT!
+%
+<adu> i'm a great hacker, but i'm horrible at thinking of things to hack
+<glyph> adu: you are my new best friend
+%
+<dash> wal-mart, purveyor of fine $9.48 chinese keyboards
+%
+<itamar|nyc> think positive thoughst and then cat /dev/urandom > file
+%
+* radix would rather go see glyph than Linus :-)
+<Viiru> radix: Why?
+%
+<dash> radix: you laugh a bit too quickly for someone who's working with a
+project with a business plan based on a pokey cartoon
+%
+<moshez> If I wanted to code with syntax highlighting, I'd just take LSD. 'My, what a green comment'
+%
+<dreid> twisted can do pretty much anything if glyph gets drunk enough
+%
+<deltab> glyph: there's something strangely fitting about being able to "from internet import delay"
+%
+<TQuid> Jesus shit. Is there anything twisted doesn't do, or at least doesn't intend to do?
+<dash> tquid: XML.
+%
+<Acapnotic> What do you get out of writing docstrings if you can't confuse, mislead, and infuriate your audience?
+%
+<gt3> i thought i had mono once for an entire year, turned out it was cuz i was using Perl
+%
+<dash> det: our chief weapons are misinformation and asynchronous networking
+<det> dash: at least you can deliver it at maximum effieciency
+%
+<dash> (breaking encapsulation for fun and profit since 1998!)
+%
+<dash> bask in the rosy glow of my ignorance
+%
+<Tv> So, now there's my way, a simpler way, _and_ the correct way? I'm getting confused.
+<Tv> Back when I was a youngster, there was just my way and the correct way :)
+%
+<Yosomono@efnet> glyph: you're telling me I'm 6 months behind you?
+<Yosomono@efnet> glyph: that makes sense, considering the time lag between film releases in the US and Japan =)
+%
+<Yosomono@efnet> Twisted: Bring Out Yer Dead (Paradigms)
+%
+<Yosomono@efnet> Fuck, what's this world coming to?
+<thirmite> yosomono: obviously something less than good.
+%
+<Tv> Mwahahaa!
+<Tv> I can encode and decode arbitrary ASN.1 structures :)
+<e@ircnet> get help
+%
+<wondr> ever since they moved over to twisted google has seemed a little bit flaky
+%
+<itamar|nyc> twisted is what medusa should've been, I think
+%
+<moshez> glyph: yes, TCP connection forwarder is good.
+<illume> why not twisted.internet.tcp_forwarder then?
+<moshez> illume: because I wanted to use the word "stupid" in code.
+%
+e2d2 (~erno@2002:d432:8efa:0:0:0:0:1) joined on ircnet
+<e2d2@ircnet> internet 6!
+%
+<dash> web in my head get it out get it out
+%
+<radix> yosomono: One of these days, I'm going to actually see what you do
+<Yosomono> radix: You will turn to stone almost immediately
+%
+<mothra> i'm not sexist, women are just a pain in the ass
+%
+<eAndroid> win still has fork though right?
+<jepler> no
+<eAndroid> hmm. no wonder my daemons don't work
+%
+<jedin> Know any good informational/instructional sites on Prolog?
+<glyph> kill yourself now
+<jedin> But I just vacuumed!
+%
+<redoz> 2 years?
+<glyph> redoz: I've been working on twisted for a while
+<redoz> apparantly
+<chrchr> glyph: Most people in this channel haven't even been _alive_ for two years.
+%
+<glyph> BLOCKING OPERATIONS ARE NEVER VALID!
+<glyph> HAVE YOU SET YOUR SOCKET'S BLOCKING FLAG TO ZERO SMALL CHILD??
+%
+<shapr> I've never used a small child as a flag.
+<shapr> not even once.
+<glyph> shapr: MAKE SURE YOU BRING LOTS OF STAPLES!
+%
+<mothra> cars have the same beauty of form as women, without the nagging
+<glyph> mothra: maybe your issues with women stem from that misunderstanding
+<glyph> mothra: to start, women are *soft*, whereas cars are not
+%
+<radix> so are you coming to IPC10?
+<dash> if you, me, moshez, and glyph end up in the same room though, we may
+assemble into a giant robot and lay waste to virginia
+<dash> and that's always inconvenient
+%
+<mbac> would it be foolish of me to wish java banished to the depths of hell and in it's place is python?
+%
+<Rainy-Day> dash: i think you know what it means but for odd reasons make it
+look like you don't :P
+<radix> Rainy-Day: he does that a lot
+<radix> :>
+<Rainy-Day> yeah
+<Rainy-Day> it's annoying as hell!
+<dash> Rainy-Day: is it really?
+<Rainy-Day> yep..
+<dash> my plan has succeeded!!
+%
+<bitPoet> all of twisted is probably like 3 lines of apl
+%
+<radix> thirmite: we're not a 3rd world country. =)
+<dash> radix: not this week anyway
+%
+<glyph> let's have some more corporations
+<glyph> then we can absolve all individuals acting on their behalf of
+responsibility and collude with the government to steal money!
+<dash> glyph: YES! where do i sign up?
+<glyph> dash: www.microsoft.com, look for "passport"
+%
+<jafo> If java had real garbage-collection, it would delete most programs
+before it executed them.
+%
+<dash> (hacking implies the use of an edged tool, java isn't sharp ;)
+%
+(context: http://yellow5.com/pokey/archive/index76.html)
+[glyph] pokey's taste for the cereal reminds me of my own preference for python :-)
+[glyph] "GLYPH THEY ARE USING WOOD GLUE AS AN OBJECT MODEL!"
+[glyph] "I WANT ANOTHER INSTANCE"
+%
+<liiwi> hrmpf. python compared to to perl is like c++ compared to c
+<dash> liiwi: so, which are you implying? that C++ is a good thing, or that
+python is a bad thing?
+<dash> liiwi: either way we have to kill you, i think
+%
+<radix> It's gonna take a lot of effort ripping reality apart
+<glyph> it's going to be almost as hard to stand idly by while you do so :-)
+<radix> I kill you!
+<glyph> no, you kill my CODE :-)
+%
+<Rainy-Day> no? jesus was like, love thy neighbour and shit
+%
+<spiv> NeuroMorphus: That's not really meaningful, though.
+<NeuroMorphus> spiv: it's not a matter of meaning, it's an assignment
+%
+<radix> man, Rune better kick ass
+<radix> this demo I'm downloading is *90MB*
+<radix> games are so huge these days
+<radix> In my time a game that filled up a whole 1.4MB diskette was big!
+<Erwin> You know what else I noticed? Today's 21" monitors are bigger than
+yesterday's 14" monitors :)
+<radix> bah :)
+%
+<bitPoet> the full name of the enterprise is probably something buzzword-
+compliant like "scalable enterprise java interspacial XML warp drive", it's
+just "enterprise" for short :-)
+%
+<dnm> Someone quote me already.
+<dnm> I'm trying to plithy.
+%
+<skreech> some say there is documentation in them there hills
+%
+<Acapnotic> "Required course materials: 1 copy of 'Java and You', an installation of JBuilder+, and a HID vomit-proofing kit for each workstation you will use."
+%
+<glyph> jafo: Are you ircing as you *DRIVE*!?
+<dash> glyph: well, duh
+<dash> glyph: cant pull over every time you want to say something
+%
+<glyph> h3x: so... you're a professional extortionist?
+<h3x> pretty much
+<glyph> h3x: do you offer professional apprenticeships?
+<h3x> i should
+<dash> glyph: gah, you beat me to it
+%
+<glyph> dash: So while we're on the subject, are there features you feel the PB protocol lacks at its lowest level that you might find useful?
+<Acapnotic> (Like the "YOU FUCKED UP AN SUBCLASSED THE WRONG THING, MORON!" feature? :)
+<glyph> Acapnotic: If python had decent metaclasses, ViewPoint would scream profanity at you personally, but until that time, I'll have to do it by proxy.
+<glyph> Acapnotic: Do you have a phone in your house? ;-)
+<Acapnotic> glyph: Yeah, it's right by the stairs. Why do you ask?
+%
+<itamar> stuouid keyboard hates me
+%
+<Nanosecond> And BTW, we taliban guys use Macs. All of us.
+%
+<Gand> <sigh> ... first day as a python programer and already I have to start writing my own functions ...
+%
+<Acapnotic> My computer is playing reggae out of thin air!
+%
+<e@ircnet> pcmC0D0c pcmC0D0p pcmC0D1p pcmC0D2c pcmC0D2p
+<glyph> e: are those the lyrics to some weird finnish music?
+<e@ircnet> glyph: not yet
+%
+<dash> guess there's a fine line between "tilting at windmills" and "hitting the fan"
+%
+<thirmite> what's a web widget??
+<glyph> thirmite: internet on a stick, on fire
+<Acapnotic> with web sauce!
+%
+<thirmite> bea: how are you?
+<bea> thirmite: not bored
+<thirmite> then why are you on IRC? ;)
+%
+<Acapnotic> something's wrong, none of the tests failed
+%
+<Tenshihan> then where does modular programming come from?
+<dash> Tenshihan: the lesser magellanic cloud
+<glyph> dash: the origin of the modular programming technique is classified!
+%
+<Rugal> Do I have to study something else in order to use twisted? i mean, is
+twisted to python how C++ is to java?
+%
+[Just another night in #Python... -ed]
+* X86BSD-H throws a ball of yarn in front of rik
+* rik watches it bounce past
+* X86BSD-H needs to get a G4 PB
+* dreid beats system-wide fetchmail with a stick
+* rik looks at X86BSD-H
+%
+<radix> but a year of python programming is like 5 years of C programming
+<radix> because in those 5 years of C programming about 4 of them are dealing
+with memory management
+%
+--> bdash (mark21rowe@chch-d109.connections.net.nz) has joined #python
+<Deep6> oh no! its the lower grade dash!
+%
+<glyph> dash: let me put it another way -- I will upgrade to 2.2, if for no
+other reason than to bitch on clp.
+%
+<det> pokey reminds me of yosomono
+<det> except on drugs
+%
+<radix> it's kind of interesting to think about twisted philosophically
+<radix> it's basically a bunch of APIs layered on top of each other
+<radix> each one making a task easier to do
+<dreid> until finally trained monkeys can do it
+<dreid> "Twisted, the framework of a million monkeys with typewriters."
+%
+<radix> rep's just this happy little lisp
+<radix> and CL is the giant living on the mountain
+<radix> rarraa I'm 8MB!
+%
+<radix> man, everyone else has cool programming fathers but me
+<radix> I'm going to be a cool programming father to a kid some day
+<dash> radix: i'm going to have a lot of kids and teach them all to play quake.
+<dash> radix: we'll be the best clan in the state.
+%
+<glyph> oooh
+<glyph> OOOH
+<radix> oh shit
+<radix> glyph just had an idea
+%
+<glyph> It's interesting that people often say "Hey, I'm looking for
+something to work on!"
+<glyph> then someone else says "Glyph's code needs a little help." then
+the original asker says "SWEET MARY MOTHER OF GOD I'M NOT TOUCHING THAT!
+I mean, uh, that's too much work or I'm not good at it. Or something."
+[...]
+<tpck> You want me to read and understand and then rewrite a 795 line
+piece of code that contains doc strings like "WARNING! This source code
+for this method may cause your eyeballs to melt."
+%
+<itamar> ok, it's JAVA TIME FOR BOYS AND GIRLS
+%
+<itamar> actually, I don't have the patience for java right now
+%
+<sayke> Acapnotic: don't make it twisted-specific
+<dash> sayke: pffft
+<dash> sayke: twisted isn't specific
+%
+<tpck> merriam-webster is nothing to me
+%
+<kingkill> glyph: i was under the impression you didn't like twisted
+%
+<amien@efnet> ? swing is bad? :)
+%
+<e2d2@ircnet> don't use threads :)
+<glyph> e2d2: it's java... you don't really have an option
+<e2d2@ircnet> don't use java :)
+<glyph> e2d2: sage words
+%
+*** Signoff: glyph[Ping timeout for glyph]
+*** glyph joined channel #python
+ * Nafai wonders if glyph is really there
+<Yosomono> He's never really been "all there"
+<Acapnotic> glyph is never *really* there, but sometimes the probablitiy
+becomes high enough that he influences internet.
+<Yosomono> He's like an electron cloud.
+<Yosomono> You can't really tell where he is at a given moment, just a
+probability.
+<Yosomono> Also, you can't tell both where he is AND how much coding he's
+doing at the same time.
+<Yosomono> This is the Glyphenberg Uncertainty principle
+%
+<glyph> I probably shouldn't think of it as an accomplishment that I manage
+to cancel all of my social- and entertainment-oriented engagements on friday
+night so I can work
+%
+<flippo> When programming languages started using four-letter names, APL was
+doomed.
+%
+<datazone> glyph: you are stupid
+%
+<radix> continuations make me want to hurt you, dash
+<dash> continuations made me want to hurt a lot of people
+%
+<radix> you're a pragmatic bastard, dash :)
+%
+<mbac> strange
+<mbac> all my life i've hated object oriented programming
+<mbac> when the problem was simply that i was using C++
+%
+<MoonFallen> how well does twisted work with xml?
+<radix> MoonFallen: PUT YOUR FACE INTO THE JELLY
+%
+<radix> MoonFallen: well, there is a very simple xml-rpc implementation
+<radix> MoonFallen: but we generally don't like to talk about it
+%
+<Acapnotic> When you're holding an automatic weapon, a remarkable number of things become your choice.
+%
+<dash> if perl is a swiss army chainsaw, this is a dynamically reconfigurable
+nanosword
+%
+<radix> skreech: hey guess what!
+<skreech> what
+<radix> skreech: exciting night tonight
+<skreech> radix: women?
+<radix> skreech: twisted release! =D
+<skreech> radix: YES!!!!!!!!!!
+<radix> hee hee
+<radix> I know you live for these moments, skreech
+<skreech> VROOOM
+<skreech> Lemme get my Twisted-Release-socks
+<skreech> and noisemakers
+%
+<glyph> backinasec,Ibrokemyspacebar
+%
+Let the record show that on Saturday, November 24th 2001, at 8:38 PM UTC,
+Glyph Lefkowitz did speak thusly:
+
+ "OK. I have a crack-laden idea.
+ or perhaps a crack-destroying id
+ Deferreds are confusing as hell
+ Let's just use threads."
+%
+<radix> I HATE METAPHORS
+%
+<dash> wow. this code does something highly entertaining, but nowhere near correct
+%
+<skreech> I can feel my brain
+%
+<nikon_> i want to live in a country thats run by beautiful large breasted women
+%
+<itamar> def revenueGenerator():
+<itamar> yield cash
+%
+<mothra> one day i want my life to be so automated that getting out of bed will
+be a configuration option
+%
+<moshez_> I love portraying prejudices
+%
+<skreech> its only 10:40pm here
+<skreech> everyones going to *sleep?*
+<Intention> I am staying up! There is much to read on the web.
+%
+[in regards to http://www.askemos.org/]
+<glyph> he is *completely* insane :)
+<dash> glyph: yes
+<dash> glyph: i hope he IRCs
+<dash> this seems like a person i could hurl abuse at for hours
+%
+<e2d2@ircnet> verwilst: debugging is easier when you read the error messages :)
+%
+[dreid] i'd like to learn Forth at some point also
+[dash] save it for last
+[dash] forth has the power to destroy minds
+[dreid] sorta the snowcrash of programming languages?
+%
+<moshez_> dash: I'm a very nice man, except in hypothetical situations
+%
+<Intention> Moral: HOORAY FOR PYTHON. IT CAN GET YOU LAID.
+%
+<glyph> steve: Are you the creator of the Grease(TM) Plan for Internet Success?
+<steve> glpyh: i'm just a vessel for Grease
+%
+<Intention> Twisted did raise me from the dead after two weeks. It is a miracle
+ of software engineering.
+%
+<Kuja> Wow twisted can do all that.
+%
+<radix> Thain: I think the point is that you'd be hacking C code, not python
+<Thain> but c is easy...what's your point?
+%
+<chrchr> dsmith: Twisted is neat, but unfortunately, it's not object-oriented.
+%
+<datazone> twisted is madness
+%
+<skreech> glyph: SUDDEN INTeRNET!!
+%
+<Pahan> hunter2: RedHat is an evil distro of death! How could you not know this?
+<hunter2> Pahan: um, as a former employee and current stockholder, I probably
+didn't know due to brainwashing. :)
+<Pahan> hunter2: Oh.
+%
+<itamar> jail time and 50K fines are great marketing tools
+%
+<mcc> My justification for java's existence is "it's not quite as bad as c++"
+%
+Broadcast Message from carmstro@zaibach
+ (/dev/pts/21) at 2:24 ...
+
+who needs IRC when you can w4llx0r
+%
+* the internet
+%
+<itamar> DIE
+%
+* dash holds up his "WILL WRITE PROGRAMS THAT WRITE PROGRAMS THAT WRITE PROGRAMS FOR FOOD" sign
+* Nafai holds up his "WILL FOLLOW AROUND dash TO WRITE PROGRAMS THAT WRITE PROGRAMS" sign
+* Nafai holds up underneath a sign reading "HOPING HE MAY ACTUALLY LEARN SOMETHING"
+%
+<comatoast> hm, you could join #artois on DALnet if you're interested in making
+a version of C++ that doesn't suck
+<dash> comatoast: uh
+<dash> comatoast: i am experiencing extreme cognitive dissonance
+%
+<resolve> we are the freedom police! you must stop this happyness right now.
+%
+<jafo> I used to hang out with this chick that ran a BBS.
+<jafo> She had a great baud.
+%
+<__funky__> so where's the real python channel?
+%
+<internet> e
+%
+<glyph> Actually, they all need for Twisted. They burn for it in the very
+core of their souls, like a vampire's thirst for blood. Programmers NEED
+twisted; existance without it is a pale shadow of the righteous glory that
+the Twisted hacker can achieve.
+%
+<chrchr> I'll like anything for money.
+%
+<thirmite_> radix: you dropped out?
+<radix> thirmite: yeah.
+<thirmite_> <GenericBoy> yay i am at college;<radix> #python has made me cynical
+i hate life
+%
+<sayke> dash: i wasn't sure what to call the system daemon/service/kernel
+module/things, so i called them "gods" and made them into a pantheon. i
+then made a creation myth as a metaphor for the system boot process, which
+i combined with a programming-as-magik analogy to form a user interface
+vocabulary roughly reminiscent of, well, crowleyian wizardry.
+<dash> sayke: you are a special and unique person
+%
+<itamar> I want to kill someone
+<glyph> Why?
+<itamar> java
+%
+<moshez> "I give thy soul to the gods of the web, may they take this offering and grant us sane protocols"
+%
+<moshez> the only reason to get a life is to get a girl
+<moshez> I'm hoping to get a girl without the seemingly mandatory life thing
+<itamar> yeah? how?
+<moshez> itamar: no specific plans
+<moshez> just random hopes ;-)
+%
+<Intention> KRIS KROSS'LL MAKE YOU LONGJMP SETJMP
+%
+<glyph> You should want me dead, you'll get all my stuff.
+<cyli> I don't want you dead -- I get all your stuff anyway.
+%
+<thirmite_> if you had 100k to spend on an engine why would you make a game? :)
+<radix> thirmite: so you can make a million dollars off of it
+<thirmite_> radix: i'd still rather buy a dedicated server in the US that did
+nothing but email dash spam on c++
+%
+<dash> i remember those days.
+<dash> the world was cold and without hope....
+<dash> twisted had not been released yet.
+%
+* dash feels the idea "3d postgres-db visualisation with twisted, pyopengl, and pygame" waft through his brain
+<glyph> dash: uh-oh, you've caught the asbahr wave
+%
+<itamar> who is megahal?
+<itamar> does he do bar-mitzvahs?
+%
+<radix> I'm fighting a huge cat with breasts
+<spiv> radix: Congratulations. I think.
+<e> i have trouble imagining how you fight with breasts
+%
+<sayke> i moved left, [the cow] moved left. i moved right, [the cow] moved
+right. i yelled "WHY ARE YOU IN MY WAY? MOVE!!", and waited a second for it to
+concoct a reply. when none was forthcoming, i dropped into stance and kicked
+it in the nose.
+%
+<dash> i can think of ways to do it but they're mostly evil. what are you doing?
+%
+<thirmite> i want one of those jobs where you get people out of cults
+- pause -
+<thirmite> by blowing up cult headquarters
+%
+<glyph> I love the fact that there's apparently a text-based Tribes-2 deathmatch going on interspersed with the argument though.
+%
+<Darkvise> I guess you could say that Windows and Linux are like two different chicks. Windows gets along with most people and it knows how to party but she's been with so many guys that you dont know what virus she might be carrying, and Linux could be some nerdy chick who may not seem that attractive on the outside but she's not shallow and braindead like Windows.
+%
+<tenth> One of our prospective clients has been asking about using MSSQL for his database
+<tenth> He can use whatever he wants. MSSQL just isn't currently supported. (So if it wakes him up in the dark hours of the morning with shrill, piping calls and cries of "Yig! Yig!" and immerses him in sanity-shattering cosmic horror, which MSSQL 6.7 has been known to do, he can't call tech support about it.)
+<tenth> "Okay... What version of BusinessMind are you using? Good... okay, what database are you using? Hmm... Well, what does it say at the top of the window? Is it a red border, or a blue border, or a shimmering band of tones and shades seeming only barely within the reach of human eyes, both confusing and terrible to look upon? Colour Out Of Space? yeah, sorry, we don't support that one. You should get MySQL."
+<glyph> Warning! Kill songs unsung while still unheard [y/N]?
+<tenth> "Please enter the number of songs you wish to kill (up to the maximum displayed next to the field) and click the Yellow Sign to continue."
+<tenth> BusinessMind For Those Who Cannot Be Named
+%
+<glyph> *whew*
+<glyph> took the call and emerged testicles intact.
+* dash points out to glyph, needlessly, that he has issues
+<radix> dash: I think they're calling them "women" these days
+%
+<johs> Oh, please. Threads ownz j00.
+%
+<ThreeSeas> maybe it's be easier if I used te metaphor of the matrix characters?
+<dash> ThreeSeas: no
+%
+<chrchr> radix: A software engineer is somebody who can extend a system without reading any code.
+%
+<glyph> funny. I'm looking at twistedmatrix.com right now and the most recent
+version is still 0.13.0
+<radix> :P
+<radix> glyph: find a QOTR
+%
+<glyph> dash: Isn't "efficiency" supposed to be your department? :)
+<dash> glyph: "crack" is my department.
+ * radix gets depressed because his department is "bitch"
+%
+<radix> What the hell was I thinking?
+<dash> radix: get used to that feeling
+<dash> that feeling is called "design" ;)
+%
+<Nafai> Wait. I think I got it to work!
+<Nafai> YAY
+<Nafai> w00t!
+<Nafai> Houston, we have a contact manager!
+<glyph> Nafai: austin.
+%
+<itamar> two more webmonkey days, and then I'm off to the USA
+* shapr hands a web-banana to itamar
+<desaster@ircnet> my god, the banana is full of ads
+%
+<cheeser> i think the general method of developing address books is to write a random number generator and use that as input for any decision making.
+%
+<glyph> e: look upon my work, o kings, and despair!
+* e@ircnet viewcvs'es
+<glyph> e: aren't you going to "viewcvs and despair"? ;)
+<e@ircnet> i will despair once i see it.
+%
+<e@ircnet> when i die i want to be dried into a scary looking dried up corpse and be used to scare young children
+%
+<Acapnotic> he just logged the fact that he got r3wt0rz3d
+<radix> he's on windows
+<radix> it comes pre-rewted
+%
+<itamar> we should lock z3p up in a protocol factory
+<z3p> self.factory.stopFactory(); self.factory.letMeEscape()
+%
+<pjarks> today is a good day to install zope
+%
+<faassen> I mean, geez, the guy thinks there is a conspiracy of programmers! a conspiracy
+related to programming!!
+<faassen> who'd have ever thought of that? :)
+%
+<radix> I am just the bombest dude in the world
+%
+"Alarm Sounds Like" -- Whoop Whoop
+%
+<noa> did anyone cause the alarm to go off just to see what it sounds like?
+%
+<ZC-Matt> You you *can* take an unwrapped object and stash it in a C module, poised to leap out at any unsuspecting transaction that wanders by.
+<zigg> ooo, above my head.
+<JimFulton> mine too. ;)
+%
+--- ChanServ gives channel operator status to dash
+<dash> magc35us: you've got 30 seconds to be witty, relevant, or at least apologetic
+%
+<Intention> How EXACTLY are cameras used to keep planes from hitting skyscrapers? Do they have laser attatchments?
+%
+<draukuWORK> moshez, i signed the zope contributors agreement today... there goes my first born
+<draukuWORK> or any first born i may borrow
+%
+<dash> glyph: go to #lisp and ask about relative pathnames. :)
+<chrchr> dash: Don't make him do that.
+<dash> chrchr: he knows better
+%
+<Nafai> What to do, what to do.
+<Nafai> No class at all next week!
+* glyph gets the "documentation" hat and starts running after Nafai
+<Nafai> AHHHHHHHHHHHHHHHHHHHHHH
+* Nafai jumps on the snowboard and takes off
+%
+<resolve> hah! java is the jerry springer of computer languages
+%
+<matju> chrchr: the web will allow us to metaparadigmatically outpace
+innovation beyond the future
+<matju> chrchr: that's why it's so revolutionary
+%
+--- shapr is now known as world
+<world> hello
+%
+<glyph> so we need to target this website to three groups -- end users, corporate shills, and open source developers
+<dash> and unfortunately JavaScript is not advanced enough to determine which is which.
+%
+<exarkun> I think there's a rather large difference between a stale twinkie and a kernel swap daemon
+%
+<stranger> ok i think i need a polymorphic language with continuations and closures to write this properly
+<stranger> should I give up and implement in C?
+%
+<yosomono> When I was done with my first test gtk app using twisted, my first thought was "is that it?"
+%
+<thirmite> srbaker: www.twistedmatrix.com - a framework for building
+asynchronous network based apps
+<dreid> in other words, doing cool stuff with little work and even less
+documentation
+[*ahem*, hopefully not for long --ed]
+%
+<glyph> if you've ever dealt with MS, it's like dealing with ... well, germany.
+<glyph> it's big and not everybody agrees on everything
+%
+* skreech runs mothra over in his shrike.
+* dreid rushes to an inventory station and grabs a sniper rifle
+<resolve> hey skreech, i just went and made myself some lunch, and you're still doing that. :) i think it's time to stop
+%
+* stampy tosses a fruitcake mortar skreech's way
+<skreech> NOT FRUITCAKE
+* stampy sprays skreech with napalm eggnog
+* skreech loses control of his shrike.
+<skreech> MY EYES
+* skreech ejects
+<glyph> dash: it's a performance art version of t2, I think
+%
+* dreid hands skreech a chaingun and dash a spinfuser
+* skreech jumps and jets.
+* Novas007 picks up a mortar
+* dash tries to work out which end to hold
+<skreech> dash: raaaatatatatatatatatata
+* moshez gets a radioactive spider to bite him
+<moshez> yay! I have spider-powers
+* Novas007 flies up to the nearest high place and begins raining mortars down
+<dreid> hah
+* skreech blows up.
+* moshez uses his spider powers to help human kind.
+<skreech> Shazbot!
+<dreid> "damn lag!"
+%
+--- dash has changed the topic to: from enemy_base import flag
+%
+* skreech throws a satchel charge in the middle of the channel.
+* dreid hides behind a generator
+<skreech> lets argue.
+%
+<skreech> THE SENSOR NETWORK IS DOWN
+* skreech pilots his shrike into the side of #python
+%
+* dreid fires his spinfuser at skreech
+<skreech> BAM!! Glyph's body flys across the map after being hit by skreech's shrike going 355kph!
+* skreech avoids various heat seaking missiles launched by mothra.
+<skreech> dreid's disc hits skreech's shrike and sits it veering into a hill.
+<skreech> Nooooo!
+* skreech 's shrike flips upside down.
+<skreech> EJECT EJECT
+* skreech 's shrike explodes in a fiery ballness of flame.
+* dreid starts saturation bombing of the area where skreech's shrike crashed
+<skreech> AAAAA
+* skreech dies.
+<dreid> skreech: :)
+<dreid> gg
+%
+<Acapnotic> Ooh, I just figured out what my first twisted.reality creation will be.
+<dash> Acapnotic: oh?
+<Acapnotic> "Being Glyph Lefkowitz"
+%
+* Intention enjoys very much being able to keep programs, editors, photo editors, and games runing for a week or more at a time without fucking up or crashing or making everything else slow. God bless younicks.
+<Intention> I never had even concieved of forgetting that programs were running until unix. Now it is like.. erm..
+%
+<spiv> Apparently my company used to be a Linux company, many years ago.
+<spiv> The website consisted of Perl CGI scripts serving stock data.
+<spiv> We moved to Windows because someone couldn't figure out how to give our customers case-insenstive website logins.
+%
+<radix> glyph: so, tell us about the trip!
+<radix> did you have fun?
+<glyph> radix: It was awesome. Sin is the best thing ever!
+%
+<radix> xihr: while moshez is indeed completely insane, he's not much of an ass-talker
+%
+<fooz> oh, mozart is "write once run anywhere" like java
+<fooz> that means it probably won't work on any platform I care about
+%
+<liiwi> moshez: gotta squish radix to do 0.15.5 soon
+(oh, my god, it's spreading - Ed.)
+%
+<Aardappel> this "I hate c++" is so old
+<dash> it's as old as C++, yes
+%
+<Blue> glyph: USE TWISTED
+%
+--> moshez (~moshez@p9.j3.actcom.co.il) has joined #python
+<itamar> look, it's moses!
+--- ameoba is now known as redC
+* redC parts
+%
+<itamar> Lesson of the day: you can't test the win32 event loop if you're not running the win32 event loop
+%
+<Yosomono@efnet> radix: It looks pretty disturbing when you see a bunch of people beating the shit out of a leprechaun who has arrows sticking out of his head
+%
+<glyph> I am tasting the pepperoni-pizza-combo flavored taste of independence.
+<matsaleh> don't let it go to your head
+<glyph> well, I still have a very strong sense of "I could crash into any of these objects at any time"
+<glyph> I figure as long as I hang on to that really tightly, I'll be OK
+<matsaleh> probably a good plan
+<matsaleh> one word of advice tho
+<matsaleh> don't drive for at least 1 hr after playing any FPS
+<matsaleh> everything looks like a power up
+%
+<exarkun> twistedmatrix.com looks a lot different in netscape than it does in links
+<exarkun> I suddenly have a much higher opinion of twisted
+<exarkun> before I thought it was all garbage. now it is all garbage with a great web page
+%
+<radix> ViperCA: you can make good websites without doing stupid shit, you know. :-)
+%
+<Pahan> foot.get_owner's_gun_through_obscure_meta_tricks.shoot(self)
+%
+<TheJester> .seen god
+<xena> God seen changing nickname to God_|Away|PersecutingAtheists ~ 52 day(s) 4 hr(s) 32 min(s) 58 sec(s) ago
+%
+<itamar> we're ripe for a syndicalist-anarchist revolution ;)
+<radix> yay!
+<radix> do you guys have a lot of those?
+%
+<radix> the sysadmin of the future is going to know twisted-shelling like the back of his hand
+%
+<Donatien_Alphonse> :) no promises - the truth may be a star, but we have a
+proper motion relative to it. Oneof my favorite quoites - a wise man I knew
+once said "Honor is truth in motion."
+<glyph> Donatien_Alphonse: A wise man I once new said "I invented the hippo!"
+It's not always best to live by the words of wise men.
+<stranger> Donatien_Alphonse: i'm beginning to think wise men should keep
+their traps shut :)
+%
+<Intention> Java and Squeak are sort of similar. They are both superdynamico and have their own widgety things and run in a VM. [Squeak] has way more colors though.
+%
+<skreech> no matter what, when I come back to my #twisted window theres always 'squish' somewhere
+%
+<red_one> hm
+<red_one> is there a python that's statically typed?
+<exarkun> red_one: the south american red python is of static type
+%
+* StevenK starts to plot a drive to Belgium, but gets stuck.
+<StevenK> Damn ocean.
+* moshez starts to plot a drive to Belgium, but gets stuck.
+<moshez> Damn arabs.
+%
+<Intention> radix: Once upon a time, I truly GOT C++. This profound body of
+knowledge was so complex, it formed a separate personality in my head just to
+DEAL with the complexity without killing me. So every once in a while, when truly
+troubled, I flip to that personality. When I come back, I have no idea what
+happened. It's NIRVANA.
+%
+<moshez> dash: you should go back for completions of logic...
+* dash points at his shirt
+<dash> "AUBURN GRADUATE (PAID)"
+<dash> i've done all the learning i'm ever going to do
+%
+<moshez> Nafai: I once met a girl on a bus. She told me her name was Li. I proved to her Aleph null is less then 2 to the Aleph null. She gave me her phone #.
+<itamar> what does her name have to do with it?
+<moshez> itamar: Lie groups.
+%
+<d1ver> python programmers?! it's not even a computer language - it doesn't even support proper tail recursion!
+%
+<skreech> radix: apparantly, in stories, chinese ISPs have responded to being blocked by the rest of the world with "take block off"
+<skreech> take off every block for great justice!
+<skreech> someone set up us the packet filter
+<skreech> <zig>
+%
+<dash> BardCat: so. what's communism?
+<BardCat> dash: It's when a boy and a girl love each other, and then there is a cabbage and a baby!
+<dash> BardCat: wait
+<dash> BardCat: i thought that was syndicalism
+%
+<gt3> perlsucks?yes:wtf_yes_it_does;
+%
+<hmmm-@efnet> sitting here seeing stuff like <ry> <exarkun@opn> really makes me feel like a minion talking to his gods :P
+%
+<dash> blag
+<dash> let's finish all this 'twisted' crap so we can write some fun stuff
+%
+<skreech> WTF. The sf.net skill profile does not have a skill for "molecular biology"
+%
+<dash> moshez: you aren't making sense now
+<moshez> dash: *now*? I'm not making sense *now*?
+%
+<stranger> Hey! I've got an idea: <byte><bit value=1/><bit value=0/><bit value=0/>....</byte>
+%
+<etcha@efnet> btw whats ry>? is it a kind of irc gateway?
+<e@ircnet> it's a bit like a mind flayer, except it also relays messages.
+%
+<Pahan> Damn, I threw a horrible insult, and got no wise-ass retorts.
+<sayke> Pahan: i was just going to say "ask me about my apathy"
+<dash> sayke: he doesn't care about your apathy.
+%
+<shapr> man I had a radix quality dream
+<shapr> it was about this guy who found a dinosaur preserved in ice, and removed its stomach, and surgically altered the stomach to be able to survive in lake awter by itself
+%
+<gt3> i had a dream guido really did get hit by a bus
+<jafo> :-(
+<jafo> He's a nice guy.
+<gt3> then somehow twisted came standard with it after dash took over
+<gt3> he seems nice
+<gt3> but nice doesn't stop a bus
+%
+<skreech> How do I keep people from reading my Perl code? Oh wait. Ha ha!
+%
+<Acapnotic> I care not for your somnable teeth. I wish only to master the multipart/form-data
+%
+<Tenshihan> why do drugs make us commit so many crimes?!
+%
+<gt3> programming should be an adventure, those damn college courses make it so its like yer joining the navy seals so you can work at sea world as a whale feeder
+%
+<Jii> "internet with python" spells twisted
+%
+<Qelf> Did you doods find it hard when you 1st started?
+<dash> Qelf: well sure
+<dash> Qelf: in fact i would characterise my programming education as being in a state of near-permanent confusion
+%
+<ElectricElf> infinity: M-x font-lock-mode
+<ElectricElf> infinity: You can set it do be on by default, but that requires
+ editing a file somewhere and I can't remember which nor what to
+ add ;)
+%
+<shapr> so, where do I buy stock in glyph? ;)
+%
+* hmh looks at unmime.c in fetchmail and cries in agony
+<moshez> hmh: eh? what would a fucking MAIL DOWNLOADER be doing with mime?
+<hmh> moshez: being too fucking smart for its own good.
+<hmh> moshez: in a very dumb way, too.
+%
+<radix> I can switch screens like none other!
+<radix> look! I just switched!
+<radix> and again!
+<radix> wee!
+<itamar> wow
+<itamar> I no longer feel bored
+<itamar> compared to you, my life *scintillates*
+%
+<glyph> yo ho ho and a bottle of internet
+%
+<kosh#zope> sorry no games are worth what xp costs in terms of the freedoms removed
+%
+<Nafai> Dang. sendmail ain't working all the sudden
+<exarkun> "all of a sudden"?
+<exarkun> Nafai: where have you been for the last decade?
+%
+<dash> the primary function of the human brain is to make witty remarks on irc
+%
+<radix> i've gotta move to one of those socialist countries and become a school-bum like princepsz
+<HappyFool> please. 'professional student', not 'school-bum'
+%
+<radix> Like, imagine sitting around with your Marine buddies in your transport spaceship, going to Mars, getting rowdied up for the battle with the space aliens
+<radix> and then you get there and a thousand marines pour out of the ships and meet a horde of 10,000 imps
+<dash> radix: AND YOUR FRAMERATE GOES IN THE TOILET
+%
+<exarkun> crack attack is life
+%
+<flippo> I was reading a book about C++ templates today, then I glanced at a preview of the Python cookbook, and I thought my ears would explode from the change in pressure.
+%
+<glyph> skreech: you think "Acquireable" is hard to spell? ;-)
+[ed: dict acquireable]
+%
+<faassen> moshez: consistency's hobgoblin has a little mind!
+* dash dubs moshez "consistency's hobgoblin"
+%
+<Overfiend> bwa ha ha ha ha
+<Overfiend> 03:28AM|<moshez> what I like about Manoj is his desire for
+ simple and small solutions. like EMACS. or dvt.
+<Overfiend> 03:29AM|<Manoj> well, dvt was _supposed_ to be simple
+<Overfiend> 03:29AM|<Manoj> it only took 2 weeks to write
+<Overfiend> that's just a classic exchange
+<Overfiend> "Well, it's really quite simple if you conceive of it as a
+ partially bounded n-dimensional manifold where n is the factorial
+ of the number of ballot options"
+%
+<TQuid> Twisted blows my mind so severely I want desperately to do
+something with it, yet I don't know what.
+<TQuid> You read about it and it's like "twisted will shortly assassinate
+Bill Gates, reformulate intellectual property law to make both BSD and GNU
+fanatics happy, and also make you a nice grilled-cheese sandwich."
+%
+<hmh> moshez: I know ESR thinks he is a god of sex, and I know his signal
+ handling code says otherwise...
+%
+<II-V-I> subliminal message: python is good
+<Yosomono@efnet> subliminal retort: damn good
+<sun> subliminal antagonism: have you tried Ruby?
+%
+<spiv> Imar: Saying "php is good because it is better than C" is like saying "maiming is good because it is better than severe maiming with shrapnel and burning oil".
+%
+<radix> why do i hang out with you geeks ;)
+<dash> radix: the money, the power, the chicks
+<radix> YES
+<radix> :)
+<dash> the self-delusion
+%
+<iLLf8d> hey all how can I get more info from python exceptions?
+<gt3> play good cop/bad cop
+%
+<stranger> too much lag. going to pub.
+*** stranger is now known as stranger[pub]
+%
+<radix> how was the [censor]?
+<radix> oh.
+* radix pats his trusty Secure-o-matic.
+<glyph> radix: Terrific! [censored] was there, and so was [censored]. We built a [CENSORED] and used it to target
+<glyph> [INFORMATION QUOTA EXCEEDED]
+<radix> whoah there, buddy.
+<glyph> Erase is delete.
+<glyph> Kill is control-U (^U).
+<glyph> Interrupt is control-C (^C).
+<glyph> Ahem
+<glyph> right. So, it went well.
+%
+<schirkaan> and i thought distro wars where over a long time ago ;)
+<radix> schirkaan: are you new to IRC? :)
+%
+<wiggy> for some reason the drugs aren't working today
+*** wiggy is ~wichert@cabal.xs4all.nl (Wichert Akkerman)
+%
+You may think I'm uncooperative, but perhaps I'm just stupid.
+Bye,
+ Mike
+--
+|=| Michael Piefel
+%
+<skreech> If MS had bought Nintendo then Pikachu could be an MS Office Assistent.
+<shapr> paperclippachu, irritation attack!
+<shapr> paperclippachu, window close immunity!
+%
+<aj> Tv: it's been around for ages, but never got put in the mainline cgi's
+ (doogie saw some bright and shiny and got distracted...)
+%
+<SteveA> I want a new builtin type for Python 2.3: zenbool
+<SteveA> It is like the new bool type, but has three possible values: True, False and Mu
+%
+<aj> willy: so the question is, do i want to try my luck with another willy
+ upload? do i feel lucky? well, do i, punk?
+%
+<skreech> kill guard
+<skreech> drink potion
+<skreech> [lag]
+<skreech> ...
+<skreech> YOU MISSED GUARD HITS YOU MISSED GUARD HITS YOU MISSED GUARD HITS
+<skreech> You can't do that when you're dead.
+%
+<rc> I'm making a game called Tycoon Tycoon. It simulates competing software companies making 'Tycoon' games.
+%
+<faassen> "Hey I could speak in Slashdot messages only" An interesting
+experiment.
+%
+<glyph> So...
+<glyph> XML.
+*** Quits: dash:#twisted [washort@d136.narrowgate.net] (Read error: 113 (No route to host))
+<glyph> Wow... just _saying_ it makes him disappear
+%
+<Overfiend> Eric Raymond got frustrated because his code wasn't getting
+ merged, and it wasn't helping him out with the chicks who only
+ give blow jobs to people whose code actually makes it into the
+ kernel.
+%
+<dash> a famous evil genius is a dead evil genius
+<dash> unless you've got a robot army or something
+<dash> and mine's on back order
+%
+<itamar> you know what causes most evilness? the WEB
+%
+<spiv> My life is a sequence of blissful sleeps interspersed by bits between sleeping (most people call those bits "days").
+<spiv> I live for sleeping.
+<spiv> It's like my natural, base state of being. The Aristotlean ideal of me is me sleeping.
+%
+<itamar> [in XMLRPC] the header saying you *used* compression is as long as the banana packet
+%
+[ 23:07:38 ] <glyph> DeepTape: Are you familiar with the Time Cube?
+[ 23:08:06 ] <DeepTape> glyph: is that a comic?
+[ 23:08:13 ] <dash> DeepTape: not.... exactly
+%
+<cyli> Your minions are like the little elves, or trolls, who make shoes.
+ Except, not really shoes: internet.
+%
+<radix> there are stick men!!!
+<dash> yes
+<dash> uml has stick men
+<radix> I LOVE UML!!!!!!
+%
+<dash> get thee down, be thou funky
+%
+<thirmite> the novelty has worn off and i once again need heroin.
+%
+<thirmite> some would argue radix on crack is a different person!!
+<exarkun> thirmite: some would argue that radix _not_ on crack is a different person
+<demoncrat> some might argue radix on crack is two different people
+%
+<glyph> and the rexec'd code would run in a thread, and could use a Bastion to frob a PB reference synchronously
+<radix> here comes the crack, fellas
+%
+<adiabatic> Every day you stay awake too long God kills a kitten. Please, think of the kittens.
+%
+<o2s@ircnet> its nott the size that matters but the code
+%
+<Nafai> After I do some preliminary testing, I will soon be using Twisted towards commericial purposes
+<Aco> like what?
+* exarkun crosses his fingers and hopes for microlaser brain surgery hardware control.
+<exarkun> Twisted: The Framework That's Cutting Up Your Brain
+%
+<sayke> "your mission, sayke, should you choose to accept it, is as follows: define r(n, b[n], x, u); where r() is reality's iteration definition rule function, n is
+the number of dimensions, b[n] is the boundry size (in each dimension) of the automata, x is the number of cell states, and u is the state of the universe, last
+iteration."
+<radix> sayke: use Twisted!
+%
+* moshez kills dash and eats him
+<krz> You feel jumpy.
+%
+<ameoba> now that everything is an object, I'm afraid you'll have to return those integers until we can verify your credit.
+%
+<faassen> I created it. but I'm not *responsible* :)
+<faassen> it started lurching around by itself..
+%
+<skreech> I dont even take a lot of whats on IRC to _brain_ much less to heart.
+%
+<dash> javadoc is a cold and demanding master
+%
+* itamar looks out the window at the view and cheers up
+<itamar> nothing like a peanut factory to remind you how good life is
+[...]
+<itamar> there's a peanut factory next to the office, and that's what I see
+<itamar> great big peanut containers, towering above me
+%
+<glyph> itamar: we should set up a really nasty looking demo with emacs and java and pb all talking to each other
+* glyph ponders code-generation-based support for PB in C++
+<glyph> OK, I am guessing that dull pain behind my eyes means I should stop thinking
+%
+[re: emacs/PB, and the implementation thereof]
+<glyph> LEXICAL-LET is cool, I don't care how it works. I don't _want_ to know how it
+works. And now I have an appreciation for why I should never, ever change PB
+again :)
+%
+<aj> mstone: the raving lunatic camp rarely manages to
+ implement stuff +effectively, so they follow the people who can... </aj's
+ theory of life, the +universe and everything>
+%
+<skreech> web
+<itamar> web?
+<glyph> skreech: INTAR-web.
+<skreech> .org
+%
+<shapr> itamar: I've heard jdk1.4 is using a modified version of the mach kernel...
+<itamar> hahahahah
+<itamar> you're kidding, I hope
+<shapr> itamar: see, YOU'RE NOT SURE
+%
+<ameoba> print "\n".join(["".join([(lambda n, f=lambda c : "\033[%dm#"%c: f(n=='0' and 30 or n=='1' and 33 or n=='2' and 35 or n=='3' and 31 or n=='4' and 34 or n=='5' and 32 or n=='6' and 37))(char) for char in line]) for line in ["%06d"%x for x in [1002,31502,314233,314251,131152,314214,411531,234562,152212]]])
+<ameoba> ex : it's a diagram of a crack-attack board +)
+<ameoba> 'cuz I can't dcc shit to shapr while he's behind that firewall +)
+%
+<__del__> is there a special method a class can implement if it does its own garbage collection?
+--- __del__ is now known as gc
+* gc collects himslef
+<-- gc has kicked gc from #zope (gc)
+%
+"Much like in the world of Frisbee, new game developers and game
+development companies should never make a statement with more predictive
+power than "Watch this!" "
+- Glyph
+%
+Brian Crowder: It's both relevant and terrifying at the same time.
+Glyph: That's the best kind of relevant.
+Matt Walker: Yes, but it's the worst kind of terrifying.
+%
+<glyph> exarkun: The issue with globals is that they make resource management nearly impossible.
+<exarkun> glyph: why kind of resources?
+<glyph> exarkun: memory, disk, process time.
+<exarkun> glyph: I don't see how...
+<glyph> exarkun: Well, let's start with a hypothetical world with twenty billion obje[Out of memory error: server stopped]
+%
+* moshez decides to call himself GNU/Moshez
+%
+<skreech> (#%&@$@
+<shapr> perl? or lisp?
+%
+<noa> "let sleeping dongs lie"
+%
+<glyph> moshez: see? xml makes people happy.
+%
+<resolve> we live in a world where some people get their jollies having sex
+ with dead people - i don't think the notion of windows supporters
+ is entirely inconceivable
+%
+<ameoba> isn't a latvia part of the female genitalia?
+%
+<glyph> blargchoo
+%
+<Yosomono@efnet> premature optimization is like that other "premature" thing, messy and embarrassing
+%
+<SteveA> I just had a very odd phone call
+<SteveA> from a researcher with the french TV station "TF1"
+<SteveA> asking about inflatable football referees
+%
+<exarkun> english am dumb
+%
+<sjj> i believe my monitor just blanked out
+<sjj> i hope i'm in the IRC window ;)
+<skreech> sjj: no use telling you 'yes'
+%
+<dash> careful with that syntax, eugene
+%
+<VladDrac> does it [Twisted - ed] make my penis grow?
+<shapr> if so, you better be careful how many people run Twisted all at once.
+<shapr> you could die of blood loss.
+%
+<shapr> in reality, it just means I can throw down some Zope stuff and then play more crack attack rather than wrestling with J2EE for months.
+<shapr> ya know, no one on #java plays crack-attack
+<shapr> I think there's a not so hidden truth there.
+%
+<EWSJames> we can be knights in shining armor if we want, but peasants who make up 99.9% percent of the people just see us as asses who wear shiny shit and talk funny
+%
+* TuxedoKamen wonders why everyone always assumes he's on linux
+<Erwin> benefit of the doubt :)
+%
+<allexpro> dash: put me in a tent and give it to moshez!
+%
+<glyph> dash: we need to come up with a "basic rules of discourse" webpage
+<exarkun> glyph: why
+<glyph> exarkun: because if one more person makes a completely unfounded assertion in front of me I AM GOING TO EXPLODE THIS BACKPACK-SIZED NUCLEAR DEVICE
+<dash> glyph: I invented the hippo!@
+%
+<dash> we've all got stupid ideas in our past
+<dash> thanks to the power of the internet, the shame associated with them need never dim!
+%
+<sjj> itamar: if you use the word 'embedded' a lot, you sound smart.
+%
+<liiwi> ah, coldness, the lovely coldness. And the ever-protecting darkness.
+%
+From the /topic on #web:
+The First Rule of Web Development is, "We Don't Talk About Netscape 4.x"
+%
+<spiv> I'm not entirely happy with it, but it works. Well, actually it doesn't. But until 5 minutes ago I thought it did :)
+ [regarding the god-cursed FTP support in Twisted -ed]
+%
+<dash> your RDF is massive and unstoppable. [to glyph -ed]
+%
+<skreech> ooooh shit
+<skreech> I have moderator points!
+<skreech> RAAAAAMPAAAAAAAGE!!$*^
+%
+<tenth> "And then you run this Z80 assembly on the resulting bytecode in the emulator of your choice to create your makefile."
+<tenth> "The inital register settings of the real or simulated Z80 are left as an exercise for the reader."
+ [the nebula build process is just not fun. -ed]
+%
+<dash> i find it interesting that your roadmap showed twisted improving most while you're in jail.
+[in reference to http://twistedmatrix.com/pipermail/twisted-python/2001-April/000037.html -ed]
+%
+<allexpro> discovering twisted is probably the best thing that has happened in my life
+%
+<sjj> dash: i'm fine with you dealing drugs, just keep them away from radix
+<dash> sjj: look, if i dont keep radix stocked, we get no releases.
+%
+<radix> /msg exarkun [lilo] HI ALL GIMME MONEYS AND LOOK AT MY WEBBARSITE
+%
+* radix harnesses the power of fudgepops for good, rather than evil
+%
+<dreid> radix: any system that relies so heavily on a human concept like trust is inherently flawed ...
+<dreid> radix: i just use gpg to encrypt my porn
+%
+<Bergenlund> how do I add twisted to autoexec.bat?
+%
+* skreech squints really hard and tries to change his neuron patterns.
+%
+<radix> MY TAPEWORM TELLS ME WHAT TO DO
+<radix> s/MY TAPEWORM/MOSHEZ/
+%
+<dash> .rhosts auth is effectively "root one get one free"
+%
+<dash> the os module is why python doesn't suck
+<glyph> dash: concrete is what makes skyscrapers not suck
+<glyph> dash: doesn't mean I want to go swimming in it
+%
+<dash> is there some connection between German and disgusting modifications to C?
+ [c.f. The Nebula Device, CLISP -ed]
+%
+<dreid> earth# apt-get install good-will-towards-man
+<dreid> Reading Package Lists... Done
+<dreid> Building Dependency Tree... Done
+<dreid> Sorry but the following packages have unmet dependencies:
+<dreid> good-will-towards-man: Depends: peace-on-earth but it is not going to be installed
+<_moshez> dreid: file a bug against good-will-towards-men
+<_moshez> dreid: unless it is in contrib?
+<Nafai> non-free, perhaps
+%
+<dreid> <xpp>
+<dreid> <xout>Hello World!</xout>
+<dreid> </xpp>
+<bruce> dreid: just fucking learn Common Lisp. :)
+%
+<exarkun> let the unwashed masses write their C
+<exarkun> you will reap the benefits of their pain and toil
+%
+<glyph> jemfinch: Are you really a captain of a spaceship from the mirror earth, on the other side of the sun?
+<jemfinch> I don't quite catch your meaning :)
+<glyph> jemfinch: Aah. Wink wink, know what you mean, say no more, say no more.
+%
+<dash> "mwahahahahahahahahahaha"
+<Nafai> you are the christina aguilera of evil
+%
+<glyph> > flirt with cyli
+<glyph> You flirt with cyli. [moshez is here, flirting with cyli]
+<glyph> > wink at cyli
+<glyph> You wink flirtatiously at cyli.
+<glyph> Glyph enters the room.
+<glyph> # glare moshez
+<glyph> Glyph glares at you!
+<glyph> # kill moshez with sword of infinite slaying
+<glyph> Glyph hits! Glyph hits! glyph hits! -more-
+%
+<bruce> i wish i was only doing an imitation of a dumb user rather than really being one. :)
+%
+<sjj> you can't be a satinist without god either
+<spiv> You can be a satanist without pants though. The world is an amazing place.
+%
+* glyph finally places the order to get his carpets cleaned
+<shapr> is carpet cleaning thread safe?
+%
+<tenth> "As a developer, I'm often discouraged by the amount of time and effort it takes to gouge out my own eyes in pain and frustration. Thanks to Gouge#.net, this distasteful task can be peformed quickly and easily by a trained professional*. Thank you, .net. Jesus, my eyes.
+ (* Professionally designed GougeWizard(TM) with your choice of animated agent character)
+%
+<resolve> i miss the days of programming computers in machine code. all this new-fangled source code is a waste of time.
+<itamar> machine code? hah
+<itamar> in my day we ran programs in our *head*
+<moshez> itamar: you had a *head*? pah
+%
+<glyph> who could forget binky?
+<radix> glyph: well, anyone who naturally blocks out haunting things so they don't have nightmares
+<glyph> radix: Kenaan disapproves.
+%
+<hornby> Slavery doesn't seem so bad.
+%
+<getchomsky> "so, mister nooning, did you know you are associating with a man named glyph, a man authorities consider to be the most dangerous jewish man alive?"
+<getchomsky> "his mastery of open source programing makes him a threat to every man and woman alive on this planet. he must be stopped. Forget everything you think you know about him, and about this "twisted" of his"
+%
+<exarkun> twisted.web has used 1 CPU second of time in the week I've had it running.
+%
+<psy> How do I stop a factory?
+<Aco> psy: syndicate strike
+%
+<exarkun> exceptions in C++ are a _huge_ mistake.
+<radix> s/exceptions in/
+%
+<exarkun> glyph: do _you_ know about super()?
+<exarkun> glyph: As far as I can tell, it's a plot, one that would be likely perpetrated by an organisation not unlike the PSU (if the PSU existed, of course), to kidnap our firstborn and empty our jars of cookies.
+<Nafai> My cookies!?
+%
+<glyph> You know, I don't think I've reached a point in my life where I said "I don't have enough emotional trauma", irc-related or otherwise.
+<dash> glyph: cool. let's go troll #c++.
+%
+<sjj> i've heard there is a /quit command.
+<ameoba> sjj : "/quit" : absurd liberal myth
+<sjj> figured.
+%
+<_moshez> itamar: so, the security people ask them what they do, and they say they are mathematicians
+<_moshez> itamar: and to prove it, they show papers with their name on it.
+<_moshez> itmaar: and then the security guys ask them to explain what the papers are about!
+<_moshez> itamar: apparently, one hasn't lived until he heard a mathematician explain to a security guy what equivariant cobordisms between symplectic manifolds are
+%
+<ameoba> c++ is 700 times faster than Python
+<princepsd> ameoba: based on? ;))
+<ameoba> princeps: something somebody said on usenet +)
+%
+<itamar> take money from elderly and weak with knife
+[itamar writes test cases for the Twisted Reality parser]
+%
+<matiu> (I have to write help files) :(
+<ameoba> matiu : you could do it with twisted.
+<matiu> ameoba: So you're saying twisted has a "help file writer" somewhere deep down?
+<dash> matiu: yes and his name is bruce
+%
+<glyph> dash: uh... what is the correct answer to the question "The short common lisp site name"?
+<dash> glyph: "it buuuuuuurns"
+<skreech> my eyes, the googles do nothing!
+%
+<skreech> Why do I feel the sudden urge to buy a nice quality florescent desk lamp?
+%
+<skreech> the woot, the woot, the woot is on fire.
+%
+<skreech> itamar: heres your nickel back.
+%
+<datazone> okay, tell me if i am crazy
+<Yosomono> you are
+<datazone> damn
+%
+<bruce> and i like doing what i enjoy in my spare time. :)
+<bruce> which, although you all might think so, isn't harassing you all to do more work.
+<bruce> although you all do need to do more work.
+%
+<snibril> JRuby? hmmm, only java ppl have to reimplement ever other lang to replace theirs ;)
+<radix> scheme people, too
+<cleverdra> Scheme people don't do that!
+<radix> how many object systems have YOU written today?
+<cleverdra> radix - today? 12, but one of them wasn't really.
+%
+<radix> excuse me for visiting my DEAR OLD BABUSHKA on her EIGHTY-SIXTH BIRTHDAY when I should be WORKING ON TWISTED
+%
+<radix> every time you make a terrible joke, a baby rabbit dies
+%
+<itamar> "We put the 's' in 'drwxr-sr-x'!"
+%
+<bruce> i've apparently gotten someone at work to clean up their act.
+<glyph> bruce: clean up their act how?
+<glyph> bruce: were they like a pedophile heroin addict or were they just checking in buggy code?
+<dash> glyph: like there's a difference
+%
+<dash> It's moshez. Remember the briefing.
+%
+<dash> glyph: so, i am trying to jump off the side of the NSF headquarters without losing my legs
+<fzZzy> reminds me of college
+%
+<itamar> IN THE INTERNET AGE YOU WILL BE ABLE TO CHAT WITH YOUR TOASTER
+%
+<itamar> I liked the "2 years C# and .NET experience" job
+<gt3> i guess they're hiring dogs
+%
+<fzZzy> how do you quit a twisted telnet session?
+<allexpro> ctrl + ]?
+<fzZzy> there's no cleaner way?
+<exarkun> calling close() on your connection's socket is pretty clean.
+<exarkun> I suppose you could call in a tactical nuclear strike on the remote host
+<fzZzy> considering the remote host is my computer right here, that would take care of everything for me
+%
+<glyph> spiv: is bugzilla bad?
+<spiv> glyph: It's... large. And perl. Join the dots.
+%
+<itamar> write a kqueue reactor
+<itamar> all the FreeBSD people will then go nuts
+<bruce> FeerBSD people are already nuts
+%
+<radix> don't let's all go break a million tests, eh?
+%
+<dash> adiabatic: citizen, you have committed an error
+%
+<bruce> i'm feeling motivated
+<glyph> bruce: yaay!
+<glyph> bruce: what flavour of motivation?
+<bruce> beating you up
+%
+<moshez> it's the holy trinity, dash, radix & glyph
+%
+<bruce> what's #ypn ?
+<glyph> bruce: the fifth circle of hell
+<moshez> bruce: young programmers' network
+<moshez> glyph: potato potahto
+%
+<bruce> allexpro is a view of the future of humanity as a group consciousness.
+%
+<exarkun> njjeeeee
+<itamar> njjeeee?
+<exarkun> ancient aramethaic warcry
+<exarkun> infamous for its ability to strike confusion into the hearts of enemies of aramathia
+%
+<exarkun> radix: lisp freak
+<exarkun> radix: go suck on a car
+%
+<ameoba> it's frightening to remember that twisted is an overgrown MUD
+%
+<fariseo> i am completely lost, all i do understand is an OS with a database backend and a scripting language, but i am missing the whole xml/.net/j2ee/twisted...
+<glyph> I'm both honored and appalled that Twisted shows up in that list :)
+%
+<exarkun> Pop up a Tkinter dialog saying "There's some information waiting for you" and do a beep every time the ethernet IRQ goes high
+%
+<hornby> 1. Create laws that promote a fair, just society.
+<exarkun> 2. ????
+<exarkun> 3. PROFIT
+%
+<moshez> skreech: I claimed the typical anti anarchist attack goes something like:
+<moshez> "say someone cracks into your computer, downloads all your porn, burns it to a CD and throws it at you?"
+<moshez> anarcho-communist: nothing. the community would reprimand him.
+<moshez> anarcho-capitalist: my private security forces would shoot him before the CD left his fingers
+<moshez> attacker: "SEE! under anarchism you'd have people throwing porn CDs at people, and people either ignoring them and shooting them!"
+%
+<rmt> Every python program needs to have direct access to a mouse over ssh!
+%
+<strib> Sorry, I'm just in the middle of a paradigm warp right now.
+<dash> strib: welcome to twisted
+%
+<exarkun> entirely not your fault, I'd say. the current behavior is somewhat broken
+<exarkun> luckily I documented it as being broken so it's not my fault either.
+%
+<kriptik> wow twisted is neat
+<kriptik> *bleeds from the eyes*
+%
+<allexpro> i said 'hello'
+<allexpro> and when i 'cat test.au > /dev/dsp'... it sounded like a tiger roar
+%
+* moshez sings the radix song
+<moshez> "for he's a squishy good radix"
+<moshez> "for he's a squishy good radix"
+<moshez> "for he's a squishy good raaaaaaadix"
+<moshez> "and nobody can deny"
+%
+<allexpro> and how do observer patterns work?
+<dash> the PSU watches your data and notifies the authorities when it becomes suspicious.
+%
+<comajelly> hrm, I wanted to snipe this guy, but he got ran over.
+<fzZzy> heh
+<fzZzy> I hate it when that happens
+%
+<itamar> night all
+<-- itamar has quit ("Client Exiting")
+<radix> me too
+<radix> heh, it's weird going to sleep at the same time as itamar
+%
+<glyph> radix: I think that the twisted vs. asyncore table should begin with this quote, though: "Our conviction is like an arrow already in flight. Your life will only last until it reaches you."
+%
+<Erwin> I recompiled XFree 4.2 with gcc 3.2-beta-from-cvs with -O42 and -march-pentium4-800Mhz and I am sure that the MOUSE CURSOR is moving 5 % FASTER!
+%
+<bruce> I CONTROL YOUR WEBSITE!@$$
+%
+--> IAmNotAPickle (slt5v@12-255-1-203.client.attbi.com) has joined #twisted
+<radix> PICKLE
+<-- IAmNotAPickle (slt5v@12-255-1-203.client.attbi.com) has left #twisted
+<radix> :(
+<exarkun> you scared it
+%
+<exarkun> heh
+<exarkun> I was at home depot the other day
+<exarkun> and they had a big rack of free AOL CDs
+<exarkun> So I took about 40 and stuck them under people's windshield wipers in the parking lot
+%
+> Linux is complicated, becasue you compile.
+Corollary: Windows is simple because no compiler comes with the system...
+[Seen on linux-il]
+%
+<sjj> dash: soon there'll be another level of college labelled "unlearn university crap"
+%
+<wzZy> arg. these infinite recursion tracebacks take forever to render in the browser
+%
+<exarkun> I'm just kidding. I'll spend on good computer books, but I failed to do any prepatory research in order to know whether any of the books there were worth anything.
+<exarkun> And I'd just spent $80 at the camping store.
+<adiabatic> wadja get?
+<exarkun> some kerosene and a cooler and a coupla chairs
+<dash> nothing like a good old fashioned book burning
+%
+<radix> you have the pokey gene
+<zigg> ack, where'd I get it from :-P
+<radix> it's random
+<zigg> triple ultra-recessive
+%
+<radix> krz: we know that glyph owns all of our souls equally
+%
+<moshez> glyph: teehee. good always loses
+%
+<sjj> if you package twisted with python, it becomes py2ee
+%
+<sjj> when does something denote enterprise? :P
+<deltab> when it's the most expensive version in its line
+<inapt> when it's terribly inefficient, but scales ;-)
+<deltab> alternatively: database
+%
+<glyph> The world made more sense when I thought software was a physical thing you sold in stores, and I wrote code in C++; making software was a lot more like mixing cement, then, not poetry or revolution.
+%
+<skreech> I'm sorry. I forgot that in #twisted, all suggestions are taken seriously.
+%
+<sjj> what do you do for your clients? :)
+<sjj> "distributed enterprise networking technology solutions" ?
+<bruce> we put DENTS in your budget.
+%
+<Jerub> All these things that would be next to impossible with php, that I can think to do in twisted.web
+%
+<timmy> so it's basically a lot of libs for doing stuff?
+[timmy becomes enlightened to the Twisted Way -ed]
+%
+<Joey> I sense disturbance in the security buildd structure.
+%
+<wzZzy> AQUAMAN VS THE GERMANS IS THE BEST MOVIE EVER MADE
+%
+<eevench> is LISP good
+<exarkun> The short answer is yes and no.
+<exarkun> You don't want the long answer.
+%
+<radix> who has the power to wield the almighty +t?
+<radix> Me!
+<exarkun> radix: I don't think you can handle the +t
+%
+<willy> you can tune a fs but you can't call a string
+%
+<Jerub> What l33t skilzz do I have to pick up to get a python job? Zope? Twisted?
+<dash> Jerub: the power to cloud the minds of men
+<Jerub> dash: I'm afraid I only have a Wand of Clouding vs. Women
+<glyph> Jerub: oh, that's easy
+<glyph> use the wand on a woman
+<glyph> women have the clouding-men's-minds intrinsic
+<glyph> so you can either make her your pet and then wander around
+near some businessmen for a while
+<glyph> or eat her corpse and get the intrinsic yourself
+<glyph> no wait, that's not how it works...
+<dash> glyph: wrong game
+ * dash twitches violently as he thinks of "nethack, enterprise edition"
+%
+<spiv> AaronSw: You should never, ever be creating a transport... Twisted is supposed to do that for you.
+<AaronSw> I should never create the tcp.Client stuff or I shouldn't manually set them as the transport?
+<spiv> AaronSw: Use reactor.clientTCP (or better yet, reactor.connectTCP in CVS).
+<spiv> Don't create tcp.Client directly either.
+<spiv> http://twistedmatrix.com/documents/TwistedDocs/Twisted-0.19.0/twisted/internet/interfaces_IReactorTCP.py.html
+<spiv> AaronSw: But of course, that API is deprecated in 0.99 (but creating a tcp.Client directly is even more deprecated :P)
+<AaronSw> Are you guys abstraction astronauts or something? ;-)
+%
+<DeepTape> Oh no, taxes! They are trying to steal your arctic circle income
+%
+<pkomarek> dash: the worst part about perl is that it is intuitive, right up until you need something to work correctly.
+%
+<dash> finally! an essential representation of the confusion.
+ [ed: referring to a diagram of twisted.cred]
+%
+<Acapnotic> Good afternoon, Agent.
+<glyph> Acapnotic: "agent"? You've been immersing yourself in the One True Game, I take it.
+<Acapnotic> What's going on out there is no game. Those guys are using real bullets.
+<Acapnotic> That last mission? I got sloppy at the end of it. Real sloppy. Barely had a leg to stand on when I got on that chopper.
+%
+<glyph> Hello .au
+<Jerub> hello .us
+<Jerub> or, alteratively,
+<Jerub> hello None
+%
+<moshez> doogie: Clint wants you to lap-dance.
+<doogie> I charge more than the normal $20
+<Clint> with or without the hat?
+<doogie> that'd be the only thing I'd wear
+%
+[About a tm.com redesign]
+<evol> But what kinda design is the goal here
+<itamar> not ugly?
+<moshez> evol: a good one.
+<dash> evol: "non sucky"
+%
+<sjj> glyph: ahh too bad, if you have a windows box it lets you use windows media player...
+<dash> sjj: he's a terrorist PPC user not a patriotic x86 user
+%
+<JerubBaal> why can religious fanatics and nigerians not figure out capslock?
+<glyph> JerubBaal: IF IS SPEAK LIKE A TELEGRAM YOU WILL LISTEN TO ME STOP IF I USE NORMAL ENGLISH YOU MAY FIND IT BLAND AND NOT READ IT ALL STOP
+<JerubBaal> Sorry, I lost interest after you shouted 'telegram'
+%
+<dash> twisted doesn't currently have any trouble with 2.2, right?
+<radix> nope
+<dash> good good
+* dash prepares to make trouble
+%
+<dash> looks like we have people who just totally fall off our radar because they're totally happy with twisted and dont _need_ to say anything =)
+%
+<spiv> glyph: You have the deepest insight into XML of anyone I know ;)
+%
+<mesozoic> I tried adding one in coil, and got more errors. I'm not sure if I'm going about it properly. Is there any other way to configure a vhost?
+<ameoba> call the vhost-bustters
+%
+<Jerub> I got in trouble for drawing a smiley face on a gantt chart.
+%
+* moshez does the evil lowering squishation resistance level dance.
+%
+<glyph> CDATA is not an integration strategy.
+%
+<nessus> The PSU? Is that that thing that I used to send $50 a year to?
+%
+<radix> "Don't expect romantic attachments to be strictly logical or rational!" [from a fortune cookie -ed]
+<deltab> do expect them to be in DOC format
+<deltab> "May all your romantic attachments be in an unreadable file format"
+%
+<sjj> skreech: you go to all classes?
+<skreech> sjj: yes.
+<sjj> skreech: why? :)
+<sjj> skreech: uni was made to be skipped
+<sjj> man, americans must be dedicated students.
+%
+* moshez doubts they realize Linux has *WAY* more brand-awareness than SCO, and possibly equal to "UNIX"
+<dash> where SCO is recognised
+<dash> it is recognised as suffering
+%
+--- ivan is now known as grub
+<grub> please donate to this IRC server I need lots of money i don't know how i can stay online.
+--- grub is now known as ivan
+%
+<glyph> I've started to think that having a lot of stable, robust stuff and a
+ lot of half-finished proof-of-concept stuff in one project is a good
+ business model
+<glyph> like "You know we can do good work, but we got bored with that bit; if
+ you want us to finish it, pay"
+<dash> glyph: good, because that's what we have
+%
+<StevenK> steven@broken:~$ ssh squished
+<StevenK> steven@squished's password:
+<StevenK> Linux squished 2.4.18-686 #1 Sun Apr 14 11:32:47 EST 2002 i686
+%
+<bruce> how are the jails in israel?
+<itamar> well, the one I was in was pretty nice
+%
+<dash> glyph: how many PSU agents did you have to kill to get that working?
+<glyph> dash: 3, and they were all waiting just inside the door. Amateurs.
+%
+<glyph> and it's considered a professional courtesy, when you are *invited*
+ into a bank, not to steal all their moneys and shoot the managers full
+ of assault rifle bullets
+%
+<moshez> itamar: you're AT WORK?
+<itamar> moshez: I am not an employee
+%
+<tenth> Doing stuff in MySQL is like getting dates at [name elided to
+ protect the guilty -ed] College... "How ugly do you want it?"
+%
+<tenth> I think we need a god verb "0wnz0r" on the
+ reality-pencil-type-thing. I'm not sure exactly what it would do, but I
+ think it may be necessary.
+%
+<dash> Saying that complexity isn't real because it "was invented somewhere else" is the most useless kind of wishful thinking
+%
+<xcabbage> mind.sf.net crashed my browser
+<dash> signs of intelligent life!
+%
+<glyph> The only thing more absurd than the technology of XML is the politics surrounding it.
+%
+<glyph> While it is *possible* that I'm smarter than you think I am, it is certain that I'm more stubborn.
+%
+<z3p> glyph: what group of programmers are you picking on tonight?
+<glyph> z3p: PyXML again
+<z3p> sounds like a blast :)
+<glyph> z3p: ugh. Actually I have a pretty high opinion of some of those people so it bugs me to have to be flaming :)
+<dash> glyph: bah, just lower your opinion of them
+<dash> no need to consider their past character, if they're wrong, they're scum@#!
+* dash twitches
+%
+<glyph> _moshez: debian really needs to make start-stop-daemon do something
+ cute, like put icons across the top of the fbdev
+%
+<glyph> we need PB for C#
+* moshez squishes glyph
+<moshez> glyph: squishy insane person
+%
+<dash> moshez: we dont have the right kind of soil to not grow wheat in
+%
+<exarkun> I try to limit myself to one major screw up a week
+%
+<tenth> in OSX, they deprecate things with hammers and nailguns
+%
+<Lan_Rover> it is my official decree that it is easier to config and run twisted as a web server than to install apache2 and mod_python
+%
+<liiwi> http://slashdot.org/articles/02/09/12/160255.shtml?tid=99
+ [the topic is "squishy Digital Rights Management" -ed]
+* dash looks at slashdot
+* dash looks at moshez
+<dash> moshez: just _what_ have you been up to lately????
+%
+<itamar> thank god I'm not religious
+%
+<dash> Hi. Allow me to express my opinion of Word now that i've gotten to know it a little better.
+<dash> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaaa.
+%
+<glyph> Stravad: whereas eval is like slitting your own throat before going
+ out for a walk so as to make the mugger's job easier
+%
+<glyph> radix: keep your eyes on the bot! we move fast.
+%
+<infinity> moshez : I won't denounce PHP... It still has its uses.. <shrug>...
+ I denounced most PHP *users* about a week after adopting it, though.
+<infinity> That language attracts more idiots...
+[Ed: infinity is the Debian PHP maintainer]
+%
+<Aco> radix: ever heard for this russian group tato/taty? pop
+<radix> Aco: nope
+<Aco> radix: well, girls were like 16 when they started. they sing in
+ther white underpants, and they wet them with water.. so you can see
+under. very interesting. want some pictures? :)
+<dash> Aco: please do not corrupt our release manager
+<dash> Aco: at least not until after 1.0
+%
+<radix> hehe yeah the whore house was awesome :D
+<radix> if you go in with a high-level the chicks pass out after you're done
+ with them
+%
+<Tv> What kind of dope is that? md5 digests _are_ 16 bytes.
+<warner> don't trust the hash, man
+%
+<bruce> hmmm. i didn't get my 11pm cron output email.
+* bruce forgets which cronjob that is though.
+<skreech> bruce: nuclear war has erupted. haven't you heard?
+<bruce> (or what machine it runs on)
+<bruce> (or what user it runs as on what machine)
+<glyph> skreech: in 2002, war was beginning
+* skreech makes a "boooosshhh" sound.
+<skreech> glyph: what happen?
+<glyph> skreech: somebody set up bruce the cron, apparently
+<skreech> glyph: main vi turn on
+%
+<shapr> c:\> vrms
+%
+<itamar> yes, but that doesn't make sense, how can you be proud of a civil war?
+<exarkuN> itamar: we won
+<exarkuN> itamar: what's not to be proud of?
+%
+<jml> is there a doc on twisted's version numbering conventions?
+<dash> jml: glyph rolls dice
+%
+<itamar> dash is *already* pre-strectched
+<itamar> he's like 6 feet
+<itamar> well
+<itamar> he's actualy 1.80 meters
+<itamar> or so
+<itamar> it's not that he's a giant insect
+%
+<_moshez> itamar: I'm agaisnt the state too :)
+<shapr> _moshez: are you purely functional?
+%
+<jml> dash: there's an otherwise normal guy at work who uses tcl as his scripting language of choice
+%
+<sjj> let me tell you something. I worked at BK for 1 year, and the veggie burgers have more meat than the whoppers, but nobody complained!
+%
+<sjj> moshez: don't kid yourself, if a cow got the chance he'd eat you and everyone you cared about.
+%
+<dash> zb0: ok, let me describe what you sound like
+<dash> zb0: "Hi. I want to drive a spike through my foot into the floor. Can someone help me with that? I know i dont need to, but there are other things i want to drive spikes into."
+%
+<itamar> it's "moshez vs. the CS profs of doom"
+<dash> itamar: i think in a war with the CS profs of doom, i'd be on moshez's
+ side.
+<itamar> yes, but your goals are different
+<itamar> inevitably your pact would weaken
+<dash> itamar: well, we'd fight a duel if we both survived the war.
+%
+<dash> |mmy: cgi is not an enterprise solution.
+%
+<_moshez> cyli: oh, yes. what did you think of my flame to val?
+<cyli> moshez: i didn't get to read all of it. glyph kept interrupting me
+ with questions of what i thought of it. and then i had dinner.
+%
+<glyph> phed: the abbreviation FAQ does not have the word "smart" in it
+%
+<cvs> Commit from glyph (changed 1) in Twisted/twisted/web: "A more expository docstring. Sometimes I'm distracted easily and I might stop in the middle of" static.py
+%
+<anonymous> i keep forgetting how much *fun* python is without zope
+%
+<glyph> radix: so ... it doesn't already do what you want?
+<radix> glyph: Well, now that I understand that what I wanted is impossible, yes. :-)
+<radix> I mean, yes, it does everything that I want, now. ;-)
+-vinge.openprojects.net- glyph changed topic: Learn from radix: if Twisted doesn't do what you want, modify your desires.
+%
+<CainKnight> Right now, I could care less about the best way to do this, or the
+ intricacies involved. What I care about is making a function get
+ called.
+<CainKnight> If that involves ritual sacrifice to dark gods, fine.
+<CainKnight> I don't care why the dark gods want chicken blood.
+<CainKnight> All I need to know right now is will they accept it and make the
+ volcano not wipe out my city.
+<CainKnight> Once the volcano is placated, then i can go back and figure out
+ that it wasn't the blood, it was the heat mixed with the iron in a
+ rich oxygen environment, and adjust the ritual properly in the
+ future.
+%
+<Yosomono@efnet> I'm so open source that I sequenced my genome and released ISOs
+%
+<glyph> one of the nice things about being american and effectively
+ culture-free
+[The next line isn't really important, is it? --ed]
+%
+<glyph> radix: there is NO bit of canada that's that close to you
+%
+<radix> ok, *6* hours ;-)
+<glyph> radix: yeah, if your car can _fly_
+%
+<Erwin> I will code your website and polish your shoes! With my toung!
+<moshez> Erwin: how do you code a website with your tongue?
+<dash> moshez: two words
+<dash> "salivaproof keyboard"
+%
+<z3p> WHY DO YOU MOCK ME UNIX
+%
+<radix> what does wifi have to do with feng shui? :P
+<dash> radix: optimal flows of internet through your house
+%
+* moshez doesn't see how you can not have a computer in the bedroom
+<moshez> I mean, what if you wake up at 4am and need to talk to someone
+<dash> moshez: walk into the other room?
+<moshez> dash: I'd need to get dressed for that
+<dash> moshez: bathrobe
+<moshez> dash: I'm sane
+<moshez> dash: my bathrobe is in the bathroom
+%
+<itamar> dash: how'd you learn?
+<dash> itamar: lessons
+%
+<radix> bathrobe is easier than boxers ;-)
+<moshez> radix: how so?
+<radix> moshez: eh, you have to deal with legs
+<radix> a swoosh around the shoulders is easier, I think
+<moshez> radix: it takes more presence of mind to tie the belt-thingy
+<jml> moshez: compared to buttons on boxers? I think not
+<moshez> buttons???????
+<moshez> jml: who makes your boxers? Chinese Torture 'R' Us?
+%
+<moshez> I'm always nice.
+%
+<moshez> jml: you're like all men, you're afraid of committing
+<jml> moshez: it's a deep seated fear of conflict
+%
+<glyph> bruce_: GPL can't force you to write code under non-MIT/BSD licenses
+<exarkun> GPL+mindflayer can though
+%
+<zen-@ircnet> which the ratio simplicity/expressivitiy of python?
+<moshez> 2.49866397309784
+<moshez> approximately
+<tigrux> moshez ?
+<moshez> tigrux: well, it's for Python2.2
+<moshez> I haven't had time to modify my calculations for the CVS version
+%
+<spiv> mjs: You've probably noticed by now that dash is only here to make occasional remarks about Twisted & World Domination... ;)
+<mjs> spiv: yeah I starting to notice... but I am sure it will become more fervent when we have a Lisp twisted implementation. =)
+<dash> mjs: when that happens, i will become more powerful than you can possibly imagine
+%
+<teratorn> as a general rule, you should never associate popularity with correctness
+%
+<dopey> fd0: what in particular about the environment is significant ?
+<fd0> dopey: some env-variables
+%
+<blanu> glyph: Itamar says I need a shell account on pyramid. I forgot why.
+<glyph> blanu: You do! For cabal research.
+<glyph> I MEAN CVS ACCESS NOT cabal research there are no blood sacrifices
+<blanu> Yes, yes exactly.
+* Acapnotic puts magnetic blood boy back in the closet.
+%
+<PenguinOfDoom> Bah.
+<PenguinOfDoom> People still say "Linux Redhat 8"?
+<PenguinOfDoom> It's "Linux version 8", damnit.
+%
+* itamar wonders if the phrase "evil spawned in dark aeons beyond the ken of man" should go in a price proposal for a project
+<moshez> depends.
+<moshez> if it's a proposal to Satan, yes.
+<moshez> also, how much would it cost?
+<moshez> is it, like, a big chunk of the price?
+<moshez> if so, possibly a more expansive description is in order.
+<moshez> like, where exactly the evil was spawned.
+%
+<moshez> "Hi, we use bit arithmetic on doubles, becuase we're really
+ stupid. We deserve what we get for programming Perl. Do you
+ have a position for us FLIPPING BURGERS?"
+%
+<radix> where the heck did caps-day come from, anyway?
+<JDAHLIN> It's something we often celebrate here in South America
+<JDAHLIN> Very traditional.
+%
+<radix> man
+<radix> I'm getting drinker's-elbows
+%
+* moshez sees the orbital lasers adjusting
+* ameoba puts on a _REALLY_ shiny tinfoil hat
+<moshez> ameoba: I'm afraid that's another myth
+<moshez> the tinfoil hats actually help us aim the lasers
+<dash> ameoba: they suppressed the laser-dispelling tinfoil in 1953
+%
+"Perl is like a normal chainsaw, but it's inflammable."
+ -- Prior-Art-O-Matic (http://thesurrealist.co.uk/priorart.cgi?ref=Perl)
+%
+<ameoba> READ THE FAQ@!#@ THAT"S NOT A MINIMAL EXAMPLERING@#
+%
+<rc> Yosomono: I was only kidding when I said, "Fuck you."
+<Yosomono> rc: Dude. Water. Bridge. Beneathage.
+%
+<Pahan> demoscene? Isn't that some Greek philosopher?
+%
+<glyph> I'm at MIT. I'm walking down the infinite corridor, and towards the
+end they have a small lab, which looks strangely like the MJ12 Level 2 labs
+in DX. In the lab are a bunch of display screens.
+<glyph> The lab has a placard next to it that says "nanotechnology center"
+<glyph> Soon as I look up at the monitor, it switches to a slide that says
+"nano-indentation".
+<glyph> I'm not kidding.
+<glyph> they are engineering the whitespace eating nanovirus _right here_
+%
+<exarkun> this server rebooted, now freshcvs is raising exceptions, the webserver doesn't run, and mailman's permission
+wrapper refuses to acknowledge setuid bits
+<exarkun> It's enough to make a guy buy a rifle and start shooting people.
+<glyph> exarkun: what kind of rifle
+%
+<jml> anyway, wasn't bruce_ writing a C implementation of spread?
+* jml decides to write a C++ one just to piss dash off
+<dash> jml: that wouldn't piss me off
+<dash> jml: that's like trying to annoy an eye surgeon by stabbing yourself in
+ the face with a pencil
+%
+<exarkun> ThreeSeas: Thanks for playing. BTW, I whipped up an autocoder last night, but it went on strike.. said it wanted a better contract.
+%
+<pht> hi, does python do threads?
+<MoonFallen> we need a dash hand-puppet to answer this question when he's not around
+%
+<ameoba> WHO WAS GENERAL TSO AND WHY ARE WE EATING HIS CHICKEN?
+<dash> ameoba: because it is SPICY.
+%
+<jml> C++ templates, a bad idea ruined by bad implementation.
+%
+<glyph> IT IS BECAUSE I AM A GENIUS!!
+%
+<mindlace> so g-d parses xhtml. Mysterious ways, I guess.
+<radix> no, microdom parsers xhtml
+<radix> g-d munges it :)
+%
+<Mifune> that is one benefit of being "god" on this project... I am my own clusterfuck
+%
+[this conversation took place at 7:16 AM, and both participants knew they had
+ clearly been awake for the previous 12 hours or so... -ed]
+<glyph> So, your schedule in space again too?
+<dash> schedule?
+<dash> my _brain_ is in space
+%
+<radix> mozilla runs on macosx, right?
+<glyph> radix: yes.
+<glyph> radix: but it's slooooooowwwwww
+<fzZzy> It's not too bad on my 933 with 1.25 gb of ram
+%
+<xyld> Java is like being naked, covered in vaseline and beaten with sticks -- I respect that some people like that kind of thing, but I'll pass :)
+%
+<jml> quoth the _moshez: Here is .lore
+%
+<fzZzy> pyn: suck it
+<pyn> An error occurred:
+<pyn> suck
+%
+<blanu> When I mentioned Twisted, Guido said it had been suffering from the
+ problem where you look at it and you can't tell what it is, but that he
+ thinks that has gotten much better lately. I then told him much praise for
+ the responsiveness and hardcore attitude of the Twisted developers.
+<radix> HARDCORE YO
+ * radix headbangs
+ * radix goes to wash dishes
+%
+<ry> twisted_ (twisted@krs-dhcp351.studby.uio.no) joined on efnet
+<Erwin> It's become SENTIENT!
+%
+[discussing Woven... -ed]
+<glyph> dmerrill: We're still working out the best way to approach this philosophpically ;)
+<spiv> "philoso*php*ically"?
+<glyph> spiv: that was the weirdest freudian slip of my life
+%
+<datazone> play that funky music dash boy
+* dash is playing that funky music right.
+%
+<ameoba> radix: I think "+q? :)" is an ETC macro...
+<ameoba> radix: it's used alot in quantum computing.
+<ameoba> "try all values, destroy universe if false"
+<ameoba> useful for that O(1) execution speed, unfortunately, it could be REALLY BAD if your evaluation function's buggy
+%
+<cluster> hehe I haven't had a decent night of sleep since I discovered twisted :)
+%
+<z3p> itamar: you can /never/ have too many monkeys
+%
+<moshez> jafo: going to give a talk today.
+<moshez> jafo: "Smooth Structure of Orbifolds"
+<dash> moshez: if i ever write an RPG, i'm going to make one of the monsters be an orbifold
+<dash> "The orbifold hits! The orbifold hits! You die..."
+%
+<radix> A Sparrow claws your face right off!
+<radix> You are dead! Sorry...
+<radix> [Info] Radix has been slain by A Sparrow!!
+<radix> The gods have mercy on your inexperienced soul.
+<radix> <1hp 103m 86mv>
+<radix> 1hp!
+<radix> no restore!
+%
+<red_one> sayke: what would happen if everyone voted consciencously?
+<sayke> red_one: what would happen if everyone beat their swords into plowshares?
+<dash> sayke: PLOWSHARE FIGHT
+%
+* vegai wears his reading bra.
+<vegai> umm, I mean glasses
+%
+(searching for "god" on Google returns http://phpnuke.org)
+<itamar> something is screwed up with google...
+<LotR> itamar: no, that's god's little joke on google
+%
+<datazone> but the real question is: "do you get to beat victims to death with a steal dildo while wearing a bugs bunny outfit?"
+%
+<exarkun> I'm gonna preempt them by mailing the list and asking what people think is wrong with it
+<Acapnotic> exarkun: will you make that a multiple choice question?
+<exarkun> Acapnotic: YES! A) IT IS TOO GOOD B) IT IS TOO GOOD C) EXARKUN IS TOO SEXY, I WANT TO HAVE HIS CHILDREN D) ALL OF THE ABOVE
+<Acapnotic> > Dear exarkum, i would like very much to use ur smtp client programme but you are too sexy and i want to have many children by you. only problem is that i am currently a man and it will take time to change for you.
+<Acapnotic> > I will understand if you do not wait for me, but i will look forward to the times we are together
+%
+<moshez> spiv: what's the date there?
+<spiv> moshez: Oct 2.
+<spiv> Admittedly we're in daylight savings, but we're also not right on the international date line :)
+<moshez> maybe Nov 2...
+<spiv> Er, Nov 2, yeah :)
+* spiv tries to act innocent, like he doesn't have a time machine
+%
+<radix> glyph needs more friends that can break into his house
+%
+<moshez> itamar: when they have an action figure of me, it will come with a squishing action
+%
+<itamar> so it's, kinda, "heh-inducing episodes occured in conjunction with
+ your girlfriend"
+%
+<jml> dash: well, I might have done something really stupid. Like embed perl code into the example by accident.
+<dash> jml: well, there's stupidity, and willful stupidity
+<jml> dash: and then there's university
+%
+<sjj> hah, they've introduced Vanilla Coke down there as well, eh? ;)
+<jml> sjj: yeah, we're really up to do. Soon, a real "burger chain"
+from the United States is gonna come here. They call themselves,
+Mac-something-or-other. :)
+%
+<glyph> that's AWESOME
+<itamar> wasn't it your idea?
+<glyph> itamar: If it was, I'm a genius
+%
+<Kengur> why cant python b compiled to native bitecode?
+<Erwin> Python IS compiled to bitecode on the Transmeta Muffin
+<inapt> what's native bitecode?
+<inapt> a sekrit language of snakes?
+%
+<icepick> go vote!
+<arma> for the shmuck, or the other shmuck?
+%
+<jml> dash: given the number of places you can stick const, "where the sun don't shine" is probably the best
+ -- jml explains good C++ style
+%
+<itamar> kill him
+<itamar> before he reproduces
+<anonymous> itamar: slightly more tact is called for
+<exarkun> Tact won't solve any problems an aluminum baseball bat won't solve faster.
+<anonymous> can't get to him - no budget money for ticket
+<anonymous> any excess budget money will go toward lucky professor #0 getting whacked.
+<anonymous> I got in trouble, because the first draft of the budget had the official "slop" line item marked as "Dr XXXXXXX hitman fund."
+<anonymous> noone disagreed, mind you - they just didn't want it officially in the budget.
+%
+<Cheez> OMG, the economy is so bad that people are willing to work in tennessee??!?
+%
+<jafo> I finally got through the Internet, but the end guy is REALLY hard.
+%
+<bram> http://advogato.org/person/Bram/diary.html?start=40
+<raph> if i were to click that link, it would: pop up a progress window; print cryptic messages about launching konq to stdout; make the kirc window small and unresponsive; and crash the gnome panel
+%
+<moshez> glyph: do you think programming requires thinking?
+<glyph> moshez: No! That is why we can automate it with robot monkeys, and we programmers must fight to earn our meager living while we are being crowded out by machines.
+%
+<saph> i'd rather have a non-robot monkey, for they are squishier and have hair
+%
+<drue> fermats last theorem is very simple, it's the proof that's a bugbear
+<raph> actually, i have a simple proof, but it's too small for me to type into irc
+%
+<exarkun> INEFFICIENT CAPITALIST YOUR OPULENT TOILET WILL BE YOUR UNDOING
+%
+<dash> it's quicker to ask python than us :)
+<MoonFallen> dash speaks words of wisdom
+<MoonFallen> it is quicker
+<MoonFallen> it's not always useful, though
+<MoonFallen> for example, last week i wanted to know a good brand of barbecue sauce. i tried asking python
+<MoonFallen> it quickly gave me my answer: SyntaxError
+<dash> MoonFallen: we dont buy any other brand
+<MoonFallen> i knew i should have looked for it at Whole Foods instead
+%
+<jml> <saph> it is easy to get a date
+<jml> <jml> saph: that's easy for you to say
+<jml> <saph> you just have to be brave and have little or no standards
+* jml fucks his clipboard
+%
+<Tenshihan> if I had a binary number 1001001 and I wanted to count the
+number of 1's in it... and the only math function I have is add, should
+I shoot my professor?
+%
+<MoonFallen> or maybe just pay his army to surrender. it would cost less than
+shipping 250,000 troops over there, i'll bet you.
+<MoonFallen> the oil companies would probably chip in too
+<MoonFallen> we could get everyone involved. like sponsoring a starving child
+in africa. except you're sponsoring an iraqi to surrender
+<MoonFallen> i wonder if we could get them to write their sponsors letters.
+"thank you for not blowing me up. thanks to your generous donation of 1 million
+dollars, instead of being dead, i am now an oil magnate in my native country."
+%
+<datazone> you're a towel
+<exarkun> a towel of IMMENSE POWER, yes.
+%
+<moshez> wow
+<moshez> I don't see how people didn't think of this before
+<moshez> if you're in competition with some windows user, just report him to the bsa
+%
+<jml> damn you all. damn you and your witty repartee and your elegant bloody framework. I'm going to sleep.
+%
+<jml> here I am, brain the size of a planet, and they make me do XML
+%
+* glyph thinks x++ should have been named "<xml type="programming">
+ <increment /> <increment> </xml>"
+%
+<raph> i was going to publish a spec for bitmap images much along similar lines as BLOAT
+<raph> ie, <pixel><color><component name="red" value="34"/> ...
+<raph> now here is the evil thought
+<raph> do up an XLST stylesheet to render it as a huge html table with cell backgrounds for each pixel
+<raph> so you can view it in mozilla
+%
+<aum> dash: do we have a 'non-profanity' chan policy here?
+<dash> aum: we have a "not acting lame" policy
+%
+* spiv wishes he never has to see another meta-argument
+<moshez> spiv: meta-arguments are fun!
+<spiv> moshez: The first time perhaps. They're always the same, though. It gets tiresome.
+<glyph> spiv: you're having a meta-meta argument now
+<spiv> glyph: My life is pain :)
+<moshez> glyph: I was afraid of having to point this out to spiv myself
+<dash> moshez: it wasn't an argument 'til you spoke up :)
+<moshez> dash: but he *answered*
+<glyph> aaaaaaaaa
+<glyph> METAMETAMETA ARGUMENT
+<jml> glyph: not it's not
+--- ChanServ gives channel operator status to glyph
+<-- glyph has kicked jml from #twisted (IT IS NOT AN ARGUMENT IF I HAVE A GUN)
+<exarkun> +1 (Insightful)
+%
+<dash> bruce: oh. intellectual dishonesty doesn't bother me when it comes to getting k5 to post our propaganda.
+<moshez> dash: intellectual dishonesty doesn't bother me when it comes to brainwashing and taking over the world
+<dash> moshez: That's what I said.
+%
+<MoonFallen> i'm looking at a perl program called tedia2sql
+<MoonFallen> the author seems pretty competent, judging by the quality of the program, but it takes him almost 300 lines what t.p.usage would allow me to do in 50
+<MoonFallen> and i'm not that good
+<glyph> MoonFallen: yes, but I'm *amazing*, and I wrote t.p.usage ;-D
+<MoonFallen> well shit, no wonder
+<glyph> and it's been hacked on by people smarter than me, since then.
+<MoonFallen> incidentally, who do you consider smarter than you? i need to hire those people or keep away from them
+%
+<MoonFallen> lol. it's nice when someone starts out a post like this: " Basically, the entire structure of your argument centers around the assumption that it's bad to have bugs in your program."
+<MoonFallen> then i know i can skip the rest of the post
+%
+<bram> I just got a call from a mechanical voice which said 'I'm sorry, I dialed your number in error'
+%
+Grocible says, "this programming job on another site demands "courage, commitment and loyalty""
+Grocible says, "Courage commitment and fucking loyalty?!"
+Grocible asks, "is this an ad for a knight's assistant circa 1450?"
+Grocible asks, "what were they called? Pages?"
+Nate says, "So that's what Active Server Pages are."
+%
+<liiwi> _pattern = re.compile('^(?P<client>[^ ]+) (?P<ident>[^ ]+) (?P<authuser>[^\[\n]+) \[(?P<mday>[0-9]+)\/(?P<mon_name>\w+)\/(?P<ye\
+<liiwi> ar>[0-9]+):(?P<hour>[0-9]+):(?P<min>[0-9]+):(?P<sec>[0-9]+) (?P<timediff>[^ ]+)\] "(?P<method>(GET|HEAD|PUT|POST|TRACE|DELETE|O\
+<liiwi> PTIONS|-))( (?P<url>.*) (?P<proto>[^ ].*))" (?P<status>.*) (?P<bytes>.*) "(?P<refer>.*)" "(?P<agent>[^"]+)"(($)|( (?P<stime>[^ \
+<liiwi> ]+) (?P<vhost>[^ ]+).*$))')
+<radix> BURN IN HELL
+%
+<radix> somebody dressed in a chicken suit came out during the concert and attacked them
+<radix> and they beat the crap out of it
+%
+<rc> I don't find transclusive folding to be that useful a programming
+language feature.
+<faassen> rc: well, it's an acquired taste.
+%
+<Stravad> Python has that instance id thingy for everything
+<glyph> Stravad: uh, that's not an OID, that's &foo; :-)
+<radix> glyph: !?
+<dash> radix: that's what id() does
+<radix> kill me
+<radix> I read that as an XML entity
+%
+<glyph> if zone transfers are bind fileformat
+<glyph> how much more complicated could this be than FTP? :)
+<dash> glyph: Prepare to be surprised.
+%
+<dash> it's like a bicycle
+<dash> but with internet
+%
+<dash> itamar: ok. well, given that Jesus did rise from the dead, one has to consider what this says about him
+<itamar> he was lucky?
+%
+<moshez> we aren't really a dictatorship
+<moshez> we're more like an anarchy, except WE ELIMINATE PEOPLE WE DON'T LIKE
+%
+<radix> sometimes i eat tums just cuz I like the taste
+<glyph> radix: that sounds like a bad idea
+<radix> you can never get enough calcium!@
+<glyph> radix: if your eyelids ever start sticking to your eyes, or you can't see or hear because a caky, white film has covered your eyes or ears, you may want to consider cutting back on your over-the-counter pharmiceutical intake
+<radix> hold up... let me raise the font size.
+<radix> Oh.
+%
+<moshez> nobody resizes my text terminals and lives.
+%
+<saph> radix: i've eaten nothing but a subway and a weird coconut thing my mom
+ made that has a pecan on it and i was kind of afraid of it, but i was also
+ very hungry so hunger won out on that one
+%
+<glyph> the god of unit testing is going to kill me for this code
+<Tv> There is no such thing.
+<Tv> I would have been dead by lightning for years now.
+* liiwi notices the lack of god of code commenting too
+%
+<queuetue> Are these actual people we're discussing, or another webcomic?
+%
+<glyph> ono! I have forgotten the sacred waterfall
+%
+<icepick> I, as someone who was a professional php programmer, can tell you: Think of the children
+%
+<PenguinOfDoom> I reject that approach. It has a suspicious lack of internet.
+%
+<glyph> itamar: uh, I *am* twistedmatrix.com
+%
+<PenguinOfDoom> CA is definitely like life;
+<PenguinOfDoom> When is says "bonus", it means "you are buried"
+%
+<itamar> i don't understand how COM works
+<MoonFallen> me neither
+<MoonFallen> i suspect it doesn't
+%
+<lgonze> ok, name a security flaw in browsers.
+<raph> "bugtraq browser" returns about 37,100 hits on google
+<raph> sorry i don't have the patience to sift through them all
+%
+<Yosomono> He's really a reasonable person, if you read his writing.
+<Yosomono> I mean, aside from the "lizards run the world" thing.
+%
+<glyph> AND NOW FOR A MESSAGE FROM OUR SPONSOR
+<glyph> Are you WEIRD?
+<glyph> Are you MADE OF INTERNET?
+<glyph> Use Twisted! Or die. http://www.twistedmatrix.com/
+%
+<MoonFallen> i know. but i've read too many horror stories. glyph gets run over
+by a truck, his source code gets acquired from his estate by microsoft, evil
+ensues
+%
+<radix> bah screw it
+* radix fakes it
+<glyph> radix: hooray for faking
+%
+<jml> will you take me, to build and to dist, in windows and in unix, till uninstall do we part?
+<jml> If any man here objects, let him speak now or ... "error: command 'cl.exe' failed: No such file or directory"
+%
+<dash> moshez: why's that better?
+<moshez> dash: um, because it doesn't necessitate Elijah
+<moshez> so it's more portable
+<jml> re-use for fun and prophet
+%
+<cow_2001> btw, for me python is love from first sight..
+<exarkun> and as with real love, it will fade after you copulate with it
+%
+<itamar> just because he was dating a 16 year old that one time he was
+ supposed to be doing a release...
+<gvanrossum> too much info, okay?
+%
+<itamar> liiwi: europe has no business
+<itamar> thus it can't make business mistakes
+<itamar> bunch of socialists living in caves banging rocks together
+%
+<liiwi> why not illegalize guns while they're at it?
+<chrchr> liiwi: Because they're Republicans. They love guns. It's _ideas_ they hate!
+<liiwi> let's start using shotguns to route packets
+%
+<radix> I love killing everything with the sword
+%
+<porridge> does python have an equivalent of C ternary "?:" operator?
+<Erwin> Python has the sextary operator, !@#$^&. Given expression x, each of the 6 operands of the sextary operator is evaluated depending on whether the expression is logically true, false, morally right or wrong or neither of those
+%
+<exarkun> if you're lucky it causes segfaults
+<exarkun> if you're unlucky it signals the Mothership that Earth is ripe for invasion and brings about the destruction of all mankind
+%
+<chrchr> fariseo: If PHP is like stabbing your eye sockets with a screwdriver, Python is like not stabbing your eye sockets with a screw driver.
+%
+<saph> i don't know. i've smoked more than my fair share of pot in my day and i've never shot anyone or raped or been raped by anyone
+<saph> the most i'd do is make some really fucking cool paintings
+<saph> that and played the best scrabble game of my life
+<saph> but that was under the influence of both pot and alcohol
+<radix> "floopy! it's a word! I swear it!"
+%
+<sjj> radix: I could smoke a pound of crack and still pronounce "nuclear" better than G.W.B
+%
+<saph> sjj: president is a minimum age of 35 (which i think is complete bs)
+<saph> i know people who are 28 who could run the country better than bushy
+<sjj> saph: I could argue I know people 5 years old who could run it better than bush.
+* warner knows magic 8-balls which could etc..
+<saph> sjj: a ficus plant could do better
+<dash> warner: that's why i'm voting for Inanimate Carbon Rod!
+<jml> In Rod We Trust
+%
+<sjj> moshez: I somewhat see what you meant about Gimli being the target of _lots_ of jokes in TT
+<sjj> moshez: it got a bit old after a while :\
+<Tv> Yes, the jokes fell a little.. short.
+%
+<Pahan> Comfort me, please.
+<fzZzy> no
+%
+<spiv> 11am - 6pm. For *five* whole days. And that's just one game! It's brilliant.
+<glyph> it's like the chanukah of professional sports!
+%
+* warner has done too much work with intermittent test failures
+<warner> my worst nightmares involve the alarm clock only ringing on mornings after I fall asleep on minutes ending in an even number
+%
+<Artimage> Says it will take 15 minutes
+<Tschechow> 15 _apple_ minutes.
+<Artimage> Actually, its already down to 4.
+<Tschechow> oh, you got hardware with an apple-minute-rate <1?
+%
+<exarkun> it probably doesn't even belong in the evil directory
+<dash> exarkun: why, do you have a "stupid/"?
+%
+<exarkun> bring on the dancing monkeys
+<Tenshihan> radix?
+<exarkun> That works
+%
+<moshez> jml: but euphemisms for sex are common in all languages :)
+<exarkun> moshez: what about lojban?
+<jml> exarkun: there's no record of any lojban speakers having sex. :)
+%
+<jml> If I were a girl, I'd fall in love with a bloke who wrote copious amounts of documentation.
+%
+<MoonFallen> i just signed up for a trial of o'reilly's safari thing, and noticed they had a "voodoo" topic category
+<MoonFallen> the first three books are about .NET
+<MoonFallen> i always had a feeling there was goat blood and zombies involved
+%
+<moshez> Debian: If It's Free, Insecure and Crap, We have It.
+%
+<jml> moshez: did you know that the average vegetarian walks around with 2 kgs
+of anti-establishment bile in their stomach? :)
+%
+<moshez> glyph: in the future, browsers will support google://blah blah :)
+<glyph> moshez: in the future, telepathic russians will rule the earth, and computers will be made of synthetic cheese!
+<moshez> glyph: before that
+%
+<faassen> I mean, do they say, okay, so bush looks like a born again christian with a faint hold on sanity but he's really a secular humanist and that's *good* or that's *worse*?
+<dash> faassen: he's a secular humanist with a faint hold on the english language
+%
+<glyph> I'm an un-american anti-semitic american jew! I love this country.
+%
+<hypatia> I'm not part of the American way because they don't let you own those kind of weapons around here :)
+%
+<Pahan> dash uses windows?
+<Tenshihan> his grandparents do -- so he's got some windows in his blood
+<Tenshihan> it's like being a quarter jewish
+%
+<dash> lament: well. inductive folds are pretty much the same thing as
+__get__ in python combined with the appropriate metaclass
+%
+<jml> hmmm
+<jml> Let me put it this way.
+<jml> When I read 'Brave New World' I imagined most of it to be in a place very much like Canberra.
+%
+<spiv> hypatia: I have seen snow!
+<hypatia> spiv: When? You mistook frost for snow one time :)
+%
+<dash> see, in my day, we didn't have those fancy init scripts
+<dash> just zeroes and ones
+<dash> and we used upstream bandwidth both ways
+%
+<Marvin--> well, yes, we know that /. is broken, but do you mean in some
+ particular way?
+%
+<jml> Argh!! what's happening to me. I'm melting..
+* Jerub hands jml a pamphlet : "So you've started to devolve into a primordial ooze".
+%
+<moshez> dash: well, I lent my crystal ball to itamar
+<moshez> because his broke.
+<moshez> and then he broke mine.
+<moshez> evil itamar.
+<itamar> I did not break your balls!
+%
+<Kengur> dash: r u familiar with the Nebula Device?
+<dash> Kengur: you.... *might* say that, yes
+<Erwin> Kengur: that's the self-destruction device Kirk threatens to blow up?
+<dash> Erwin: Kirk's was before the invention of C++, though
+<dash> Erwin: so they couldn't really imagine the horrificness
+%
+<dash> sourceforge's CVS server is secretly dalnet!
+%
+<moshez> Yosomono: I AM INSANE!!!!!
+%
+<etrepum> webtastic
+%
+<liiwi> hrmpf. concurrency issues are nasty
+* Tv gives liiwi two forks and invites him to join the table.
+ <liiwi> Tv: can I bring my leatherman?
+<Tv> Well, only if you give up one fork.
+<Tv> We need to have the same number of tools and eaters.
+<Tv> Otherwise it's quite unfair :)
+%
+<moshez> the common definitions say that after a person rises from his grave, he is the undead.
+<dash> moshez: Buffy definitions are not common definitions.
+<moshez> dash: Buffy could kick your ass.
+%
+<ameoba> EVERYBODY GET STONED!!!
+<dash> ameoba: furthermore
+<dash> ameoba: everybody MUST get stoned
+<ameoba> YAY EQUALITY
+%
+<z3D> dash: yup ... remember the word 'enterprise' ?
+<dash> z3D: YES! it is one of my favorite words
+%
+<chrchr> datazone: jwz didn't invent "If the only tool you have is a hammer . . ." any more than the French invented tongue kissing.
+<datazone> chrchr: my grandpa invented french kissing
+<datazone> and i will be damned if i sit here and listen to you say that he didnt
+%
+<exarkun> '''The MODE command is a dual-purpose command in IRC. It allows both usernames and channels to have their mode changed. The rationale for this choice is that one day nicknames will be obsolete'''
+<dash> yeah, and the marxist state will wither away
+%
+* radix is a monkey with a bamboo stick
+* Kengur tries to trade banana for a bamboo stick
+<radix> hah!
+* radix beats kengur on the head and steals his banana.
+<radix> You don't need trade when you've got a bamboo stick.
+%
+<lament> Software Engineering is basically a set of techniques for making bad programmers write good code.
+<dash> lament: Which is why it doesn't work.
+%
+<Yosomono> ameoba: my hand made me sleep on the couch :P
+%
+<tansaku> mathematics is so about sex
+%
+<chrchr> Spalding Grey made the point that comedians (and comic artists!) might spend a long time building up to the punchline at the end of the joke, while a good storyteller can have the audience laughing throughout the story.
+<chrchr> Discuss.
+<exarkun> Spalding Grey is a monkey in red suspenders.
+<chrchr> exarkun: Excellent point.
+<chrchr> Everyone, how does exarkun's point about Spalding Grey being a monkey in red suspenders make you feel?
+%
+<kiko> is it just me or is the term "software design" too vague to shake a stick at?
+<exarkun> kiko: get a bigger stick
+%
+<jml> dash: is quoting pokey like mentioning the holocaust on usenet?
+<dash> jml: nope
+<dash> jml: quoting pokey is recommended practice
+<jml> dash: but, it makes retort impossible.
+<dash> jml: you're beginning to understand
+%
+<radix> HORRIBLE JAVASCRIPT ARGh
+<moshez> radix: when a teenage suicidal brooding girl builds a web site, what do you expect?
+%
+<itamar> glyph: spyce.sf.net
+<glyph> GYAH
+<glyph> yes, I've seen this before
+<glyph> I believe it was looking at this page that I coined the phrase "captain Dimwit McStupid"
+%
+<fatjim> quotemaster: i know a guy on gimpnet who wrote his entire website in bash
+<fatjim> quotemaster: then he wrote an ircbot in bash
+<fatjim> quotemaster: then his head exploded and we had to burn the entire building to prevent an epidemic
+%
+<dash> Iä, const char* fhtagn
+%
+<radix> When I take PCP, I pretend that I'm half-leprechaun-half-cheetah
+<dash> "You feel jumpy."
+%
+<Acapnotic> Are there sound effects that accompany the speaking of "Debian Project Leader"? Trumpet fanfare or choir of angles or the hushed whispering of the cabal or something?
+<_moshez> Acapnotic: the Empire music from Star Wars
+<_moshez> dam dam dam dadadam dadadam
+%
+<psy> good thing my 15yo sister also smokes rollies =P
+%
+<dash> i used to think ms creighton was just insane, but i'm reminded of a .sig on the parrot list
+<dash> "the difference betweeen insanity and genius is measured by success"
+%
+<sjj> itamar: you'll be sorry when the VIC is mainstream! you'll all be out of jobs HAHA
+%
+<Logan> You might as well ban dancing next!
+<Yosomono> Logan: We don't allow that sort of reckless behaviour here.
+<Yosomono> Logan: It leads to fraternization amongs youngsters, and eventually PREMARITAL SEX
+%
+<Logan> chrchr: Are you saying this channel is boring?
+<Logan> I think I saw something funny in here once.
+%
+<glyph> oh. where is saph?
+<exarkun> she got a job
+<glyph> Hooray!
+<exarkun> It's a mixed blessing
+<glyph> What's in the mix?
+<exarkun> I get to hear about how I don't have one a lot :P
+%
+* zookoasleep sleeps furiously.
+%
+<raph> [WebDAV] probably has all the disadvantages of a stateless protocol combined with the problems of actually implementing state
+%
+<chrchr> If I had dignity, I'd have to start wearing pants to work.
+%
+<amybah> THE REVOLUTION WILL BE TELEVISIED (to drm compliant devices)
+%
+<dash> "we wisssssh to sssquissssh, my preciousss"
+%
+<itamar> glyph freaked out my grandmother
+%
+<dash> [From the GPL FAQ:] "If the program dynamically links plug-ins, and they make function calls to each other and share data structures, we believe they form a single program"
+<dash> the days of "one process == one program" are coming to an end, i believe
+<SamB> o/` Python is a totally different way of thinking, which doesn't have much to do with linking o/`
+%
+<glyph> moshez, you are only allowed to be excited about one insipid american cultural icon at a time
+<moshez> glyph: have you not realized that at heart, I'm an american teenage girl :)
+%
+<moshez> I prefer the ones who fully embrace, knowingly and publically, the commercialization
+<moshez> than the ones who commercialize by being "non-commercial" (Eminem is the prime example here)
+<Aco> whatever... i just like girls in white panties
+%
+<lstep> It's not my fault, my wife keeps connecting under false identities to monitor me!
+%
+<wzZzy> I tried to use woven.guard sunday night
+<hazmat> and?
+<wzZzy> hazmat: glyph's powers of obfuscation are considerable
+%
+<icepick> to the batlaptop!
+%
+<moshez> radix: oh, and did I tell you I'm officially a sock puppet now?
+%
+<wzZzy> what's up mjs
+<mjs> wzZzy: nothing much, just got home from school... yourself?
+<wzZzy> mjs: workin'
+<mjs> that's what you are always doing. =)
+<wzZzy> that's cause I have a job
+%
+<raph> but if you are going to combine a scoring system with eigenvectors, it's a lot easier to think about linear things than funky bayesian formulae
+<wmf> eigenbloggers are useful, too
+* raph prepares to eigensmack wmf
+<raph> prepare to compute the eigenvalue of _this_, fool
+%
+<exarkun> nethack should be made illegal
+<exarkun> it corrupts the minds of otherwise upstanding young men
+<dash> exarkun: dont make it illegal 'til after i get this fellow into gehennom
+%
+<glyph> I am getting my design on.
+<glyph> (For those of you not already in the aisles, this means: RUN!!!!)
+<glyph> I _really_ like this one
+<glyph> it may not be appropriate for the main site, but we're going to have to use it someplace
+%
+<glyph> radix: Your credulity is not a requirement for precipitation!
+%
+<shapr> no one wants to play with my monads :-(
+%
+<ThreeSeas> what is melatonin?
+<ThreeSeas> is that like another programming language?
+%
+<gus> doesn't the phonetic representation (Zo["o]l.) mean that it should be pronounced "Zoul"?
+<glyph> I don't know...
+<glyph> I can't read phonetic.
+<gus> and all this time I thought you were phonecian.
+%
+<exarkun> Oh. The infamous "Windows sucks" bug.
+%
+<datazone> you know, america has alot of different military orginazations
+<radix> yes, maybe they'll get into a war
+%
+<radix> i want to make sweet love to twisted.python.components
+<ameoba> radix : it probably wouldn't be hard to whip up an adapter +)
+%
+<jml> _moshez: having dates would be kinda cool.
+<itamar> jml: if you take a shower every day, you'll find it's a lot more
+ likely
+%
+<trawa_@ircnet> im trying to get started with web services, but i need
+ some software - i think i read all of ibm's articles on that, read
+ about 4suite - does anyone know of sth litghter, smaller in size and
+ easier to absorb?
+<dash> trawa: hmm. what do you mean by "get started with web services"? :)
+<trawa_@ircnet> well im trying to port my html application to flash mx
+ but dont fell like using cold fusion..
+%
+<exarkun> What powers the orbital lasers
+<exarkun> I always assumed it was the Sun, but now I don't think that would work too well
+<exarkun> Unless we are anticipating zero resistance after the destruction of the Sun
+<exarkun> This sounds like a serious flaw in our plans, then
+%
+<floam> itamar: think of how the anonymous bathroom fits in with the way our society works
+%
+<floam> the paper towel represents the customers
+%
+<dash> perl has EVERYTHING.
+<brc> except buffer overflows
+%
+<wzZzy> we should write an os
+<itamar> YES
+* itamar starts a sourceforge project
+%
+<chrchr> There are some dead/preserved carpenter ants in a bag in the mailroom of the building where I live, and a sign that says, "THESE ARE CARPENTER ANTS. BEWARE!!"
+<datazone> chrchr: why do they assume ants can read, and if they can read, that they can read english?
+%
+<sayke> those whom the gods would destroy they first make l33t
+%
+<shapr> ucking keyoar
+%
+<drewp> this twisted thing really is a text adventure game
+<dash> drewp: What a ridiculous notion.
+private message from dash: wuh oh, drewp is on to us
+%
+* itamar goes to french class
+<radix> FRENCH class???
+<radix> Don't you mean FREEDOM class?
+%
+<glyph> my condolances
+<itamar> if Thunder- solves it i'll be ok
+<itamar> writing a C++ extension to open a fucking find dialog is soooo fun
+<glyph> oh
+<glyph> I meant "my condolances on failing to stop the war" :)
+%
+<moshez> glyph: I have an ethical question for you.
+<glyph> moshez: my answer, as always, is "kill them all and let god sort them out"
+%
+<snibri1> i wonder if those boost ppl ever sleep
+<jemfinch> snibri1: why?
+<AdamV> snibri1: Nightmares from C++ template coding?
+%
+<Artimage> Amazon wish lists are really a double edged sword.
+<Artimage> This guy made me his amazon friend... so I checked out his list...
+<Artimage> lots of 'how to make your own porno movies' books... I now know WAY TOO MUCH... but I have no clue who he is.
+<Artimage> I would have to know someone at least a week before I'd be interested in knowing this.
+%
+<treker> pyn ur fuckin hot
+<treker> :-Plol
+%
+<moshez> marz: don't be scared!
+<moshez> marz: the squishing is fun
+<moshez> ask anyone
+<dash> it isn't fun
+<dash> it's like a tiny genocide
+<moshez> marz: ok, ask anyone except dash
+%
+<moshez> dash: what is your profession, again?
+<dash> moshez: "irc junkie"
+<moshez> dash: good money in that, huh?
+<dash> moshez: money?
+<dash> moshez: now that you mention it
+<dash> most of my jobs lately have come from irc
+%
+[re: porting the rewrite of jelly to Scheme]
+<radix> So is it still really mutaty?
+<glyph> It's so mutaty you're going to be using 'set' with *two* exclamation points
+%
+<jml> you ever heard about cosmic conflicts between the forces of order and
+ the forces of chaos? Well, Canberra is like Order winning, stomping over the
+ frail corpse of chaos, and all spontenaity, surprise and flexibility
+ disappearing from the world.
+%
+<Spec> Damn you pyn. You are now my greatest adversary.
+%
+<-- Spec has quit ("The bell that controls our lives has released us into the world....woot.")
+%
+<exarkun> What's a good, low-cost way to notice when a new file exists in a directory?
+<SamB> exarkun: open the directory for reading, read to the end, and use select? (crazy!)
+%
+<coderman> you ever see my callback templates for use with static and member functions?
+<coderman> it works nicely, which is a credit to the STL, but it is also so incredibly ugly it makes my head hurt
+<coderman> http://cubicmetercrystal.com/alpine/gen_html/base/AppUtils/AppCallback.h.html
+<zooko> What the fuck, man? Why are you memcpying these void*'s? Where is callbackData_ defined? What are all these things templated on "class argument Type"?? AAAAiiiigh!
+* zooko holds one hand over his eyes and clicks blindly with the mouse in the attempt to hit the "close window" button.
+%
+* Acapnotic puts on his "in MY day, we had to write our modelines by hand, with nothing more than some loose notes by matt walsh, the back on an envelope, and the blood from our own hands..." pants.
+%
+<jml> the phrase "unstable reactor" makes me feel a little nervous
+%
+<etrepum> dance dance hack?
+<radix> hack hack revolution
+%
+<pyn> <widada@ircnet> this is what i see: "<pyn> <radix@fn> Huh?"
+<pyn> <princepsd@ircnet> do you want to see somethingelse?
+<pyn> <princepsd@ircnet> ;))
+<pyn> <princepsd@oftc> or is this to much for you? ;))
+<pyn> <princepsd@efnet> the bridge confuses you?
+<princepsd> you can't keep track? ;))
+%
+<exarkun> why would someone mount a swapfile from an NFS mount?
+<MoonFallen> exarkun: to bring about an anarchic dystopia
+%
+<princepsd> marxist witted is a anagram of twistedmatrix
+%
+<exarkun> z3p: conch is making me sad again
+<z3p> exarkun: conch only does ssh, it is not responsible for also making you happy
+<exarkun> Oh. Crud.
+%
+<bram> it's nice being a cult leader :-)
+<bram> now I must get my minions to start giant bonfires in the woods and do ritual dances around them
+%
+<Yosomono@efnet> glyph: Does it amuse you sometimes to realize you've created an entire genre of coding for people?
+<glyph> Yosomono: "sometimes"? It amuses me constantly. It's become the central staple of my entire sense of humor, as well as my livelihood.
+<glyph> It is easily the most amusing thing in my life.
+%
+<dash> if python is an orchestra, overloaded operators are "miscellaneous percussion"
+%
+<radix> Don't be a consumer! Live off the land
+<itamar> there are no pants-trees in nyc
+%
+<fzZzy> what good is planetary consciousness if it can't open an arbitrary socket?
+%
+<jml> can someone briefly explain componentized in the context of the web registry?
+<spiv> jml: You've got an internet cloud, right? Only, you're reading isometric, so you really want an internet *cube*.
+ So, you take your componentised cloud, and ask it for a cube adapter. Then exigency girl arrives, kicks your ass, and everyone
+ is happy.
+%
+<spiv> As far as I'm concerned, the meat pie is the ultimate unit of currency.
+%
+<Noen> fear twisted, because its leet
+%
+<glyph> jml: I PROMISE I really thought popsicle was a good idea at the time.
+%
+<jml> balsa is crying out to be re-written in Twisted
+<jml> 'heal me' 'heal me'
+<jml> 'I don't want to be threaded'
+<Acapnotic> yeah, lots of mail programs say that
+<jml> Acapnotic: It must be very easy to write GUI mail programs that suck
+<Acapnotic> it is, but a lot of people have gone above and beyond the
+ bare minimal effort required
+%
+<warner> Activate the Flux Condensor!
+<MoonFallen> We can fix it by patching the wireless with a subspace access point!
+<itamar> the powerbook's dilithium crystals are not in tune with the mr814 access point
+* warner looks through his toolbox for a transdimensional flux agitator
+<warner> uh-oh, we've got a level 3 resonance singularity
+<MoonFallen> But it'll be risky! We've got to get the harmonics oscillating
+ correctly before Buffy comes on, or we'll be dead in the water.
+<warner> Right, you align the warp power conduits while I re-fractalize the
+ positronic emitters. We meet back on the engineering level in 40
+ centons.
+%
+<warner> happy tests, fat buildmaster
+<spiv> warner: I can practically hear it yelling "Feed me, warner!" from
+ here ;)
+<spiv> Heh. Now I'm envisaging a "Little Shop of Hackers" with the
+ buildmaster as the plant (and the buildslaves for tendrils!), and poor
+ warner as Seymour :)
+<warner> FEED ME!
+<spiv> For some reason, I keep thinking of _moshez as the dentist ;)
+%
+<exarkun> radix: [dash] *is* a lazy sob
+<exarkun> radix: but that doesn't mean he's wrong
+%
+<itamar> I don't want my name in the windows registry
+<itamar> it's probably bad luck
+%
+<exarkun> it's JAVA, if you didn't want to type FIFTY LINES to do ONE STUPID THING you wouldn't be using it, right?
+%
+<jml> _moshez: yeah, but that's obviously an imposed patriarchal paradigm
+ that's entirely foreign to the implicit metanarritive.
+<flax07> not to be ot - but can anyone give some newbie help
+%
+<freeside> On a scale of One to AWESOME, twisted.web is PRETTY ABSTRACT!!!!
+%
+<rik> note to self: do not advise people to use a Deferred when in #c.
+%
+<saph> dash: when you take over the world, can i be in charge of leather and vinyl active wear?
+%
+<Nafai> w00t w00t w00t w00t!
+<Nafai> I don't understand all of the code, but it works!
+<Nafai> I guess I should check it in.
+%
+<moshez> glyph: I don't care about actual people
+<moshez> glyph: I care about not screwing up Debian
+%
+<arno> what are the main things to change going from [bsddb] 3.3 to 4.1?
+<icepick> moving around when you open transactions
+<arno> sounds like money laundering strategies...
+%
+<gt3> welcome to the world wide wtf
+%
+<glyph> warner: you want to port twisted to PyMite?
+<warner> now *that* would be entertaining..
+<Nafai> ...for various definitions of entertaining
+%
+<zooko> I don't know if I'll have much time, but my goals are: 1. test_ent.py, 2. ent.py, 3. make znff.py use ent.py, 4. rule the universe
+%
+<hypatia> moshez: That's OK, you can feature in my diary as "Glyph's bitch".
+%
+<akrherz> quotacheck <-- Do I have quota left for more questions :)
+<radix> akrherz: Insert $.25
+<akrherz> where? Can I just give my credit card?
+<radix> akrherz: That'll do.
+<akrherz> wow, the internet is fantastic
+%
+<glyph> dreid: so you have some free time?
+<dreid> glyph: not really but i could always sleep less
+%
+<jml> any world knowledge-ables about?
+<chrchr> jml: YES.
+<chrchr> jml: Oh. You mean twisted.world.
+<jml> chrchr: yes.
+<jml> chrchr: I am not aware of any other.
+%
+<wumpus> google, froogle, when are they going to start making names that make sense? :P
+<wumpus> even the twisted module names make more sense
+%
+<dash> wumpus: names that make sense are at an end
+<dash> wumpus: they have been all trademarked
+%
+<chrchr> radix: Are you at NASA? Do you see any of the aliens?
+<radix> No.
+<radix> chrchr: They don't keep the aliens at Goddard, anyway
+%
+<_moshez> dash: we need to make Twisted into, pardon the comparison, Zope
+%
+<Holocaine> moshez: I think your bad wrap comes from the fact that all of us
+ are far too postmodern for your version of correctness. =)
+%
+--- itamar has changed the topic to: "We reject kings, presidents, and voting. We believe in rough consensus and running code." -- David Clark
+<dash> also, giant robots.
+%
+<itamar> better not have children anywhere [POWERFUL CORPORATION'S NAME ELIDED -ed] can fly lawyers
+<etrepum> hah
+<etrepum> they're not that bad
+<itamar> that's what they all say
+<itamar> and then it's "but I didn't think they take *my* little Joanne!"
+%
+<dash> fortunately not all people who call themselves christians are bloodthirsty imperialists
+%
+<glyph> dreid: you want to talk about the web?
+<dreid> glyph: yes
+<glyph> dreid: I THINK THE WEB IS TERRIBLE
+<dreid> well there is always gopher
+%
+<zoyd> someone help me with using dictclient.py
+<zoyd> the dict.org client that is.
+<tappintap> zoyd: what are you trying to do. Start at the beginning, like: I
+ got up today, and I wanted to paint the shed.
+%
+<dash> there is a particular sense of fatigue that i have come to associate with the aftermath of attempting to troubleshoot windows problems
+%
+<itamar> oooh
+<itamar> Windows Server 2003 CD
+<itamar> 180 day evaluation
+<glyph> itamar: damn! that's half as good as the full product
+%
+<ivan> i can't wait till palladium!
+<ivan> cheat-free gaming at last
+%
+<jml> spiv: some might call it rewriting, I call it "refactoring from zero"
+%
+<Nafai> I need to find a reason to learn Woven. :)
+<exarkun> it will get you laid
+* Nafai thinks
+<Nafai> I'm not sure I believe you.
+%
+<jml> if I'm stuck in windows, what's a good browser?
+<jml> preferably one that implements the Aquinas protocol so I can get infinite bandwidth with no hardware upgrades
+%
+<radix> THE REAL WORLD IS JUST LIKE HIGH SCHOOL
+<radix> ARAGH
+* radix shoots himself.
+%
+<exarkun> "While working on a web framework late one night, Mr. Preston was sucked into an http vortex and trapped on the internet. Now he roams from website to website, crying at code embedded in malformed html templates and exploiting cross site scripting bugs."
+%
+<AdamV> SamB: PHP's basic control structure is the "database timeout error".
+%
+<tjs> I was telling the guys at work about woven, and they asked me to implement it in php.. I told them, without battering an eyelid, that it was totally impossible.. sometimes you just have to stand up for whats right
+%
+<tjs> they have xml parsers for php
+<dash> I AM NOT EVEN GOING TO THINK ABOUT IT
+%
+<WuN> my school went on a trip to Queens U last week (like we stayed the week
+ in the dorms and such), and i took java... i learned 3 things that week:
+ 1) i dont like java 2) lectures suck 3) not having parents is amazing
+%
+<Yosomono@efnet> chrchr: In MMORPGs, I typically play female characters too.
+ Mostly because if I'm gonna spend a lot of time looking at
+ this person's ass while they're running around, I wanna see
+ something decent.
+%
+<Tv> How does everyone feel about getting all dirty with low-level networking in twisted?
+<Tv> I'm thinking ip, udp etc.
+<Tv> As in, "here's the full packet".
+<itamar> personally, it fills me with an unholy glee
+%
+<itamar> Tv: there are people who will offer to marry you if you release this
+%
+<jml> if only I could eat whitespace
+%
+*** prell (~prell@106.165.8.67.cfl.rr.com) has joined channel #twisted
+<prell> glyph: thanks :-)
+*** prell (~prell@106.165.8.67.cfl.rr.com) has quit: Client Quit
+<liiwi> wow, a drive-by thanking
+%
+<exarkun> radix: also, I need a crossover cable.
+<radix> wth are you using cables for
+<radix> cables are gross
+<etrepum> because sometimes you want bandwidth
+<radix> yeah right!
+<etrepum> well you can go play your infocom games and I'll transfer large files
+%
+On 2003.05.23 18:54, Glyph Lefkowitz wrote:
+> On Friday, May 23, 2003, at 06:45 PM, Bob Ippolito wrote:
+> > The next big thing is to fantasize about nonexistent programming
+> > languages that make good compile and runtime decisions for you.
+>
+> Hey wait! I'm *really good* at that! Let me tell you about this paper
+> I read on linear objects...
+
+[Later that day, on IRC...]
+<radix> dash: dogg! glyph is cutting up
+<radix> dash: latest post to t-p
+<dash> radix: Yeah, i know he hates me and wants me to die
+<dash> radix: why, what's new?
+%
+<dash> cfork: if all you have are nails, there's no need to pick up the
+ biggest hammer with the poison-ivy oil on the handle every time
+%
+<zooko> I once had a party in Amsterdam, and there were two live webcams, and I was worried that people would
+<zooko> accidentally do something on camera that they didn't intend to.
+<zooko> So I spent a long time making great big signs, hard to miss even if you are fucked up, saying
+<zooko> "THERE IS A WEB CAM IN THIS ROOM" and stuff like that.
+<zooko> As soon as the party started some people pushed the cam aside and started doing lines of coke on the desktop.
+%
+<chrchr> datazone-work: Some people dominate the world because they can't hold down a regular job and like the flexible hours that world domination offers.
+%
+<fzZzy> dang. when there are so many layers of abstraction you don't understand it's tough to do a minimal test :(
+<fzZzy> and when glyph writes no docstrings
+<fzZzy> now I know how people feel trying to use woven
+%
+<Tv> Enough rope to shoot a foot in your mouth.
+<_moshez> enough mixed metaphors to grab a bull by its horns?
+<Tv> Enough mixed metaphors to grab a bull and eat it, too.
+%
+<glyph> jml: world needs help
+<spiv> glyph: Sound like you need to put an ad out for a superhero
+<glyph> spiv: all too true
+<glyph> world.save()
+%
+<moshez> the breasts are part of the cognitive dissonance
+%
+<glyph> So, how do I tell distutils to compile/link with g++ rather than gcc?
+<spiv> glyph: By typing your commands IN BLOOD
+%
+<cehteh> >>> foo()
+<cehteh> Segmentation fault
+<cehteh> doh
+<whitestar> cehteh: sucks to be foo()!
+<deltab> I pity the foo()
+%
+<itamar> ambivalent?
+<gus> kinda
+%
+<radix> how many people are we going to get posting to the list "How do I do X? I can't use <perfect solution>, it doesn't fit my design" today?
+%
+<sjj> spiv: where does your family live?
+<spiv> sjj: Singleton.
+%
+<limi> I need some sex to adjust my sleeping patterns
+<limi> the only thing that *really* works ;)
+%
+<sjj> dash: you certainly are an enigma wrapped in a riddle wrapped in a hat.
+%
+<cyli> actually I signed on because i was curious to see what people thought about the stem cell debate
+<radix> cyli: I'm with Dream Theater on that one
+<glyph> radix: You believe that the decision on the debate should involve a melodic but very complex 10-minute sample montage?
+<radix> glyph: Hell yes
+%
+<Alea> Just spent the last 6 months writing const-ridden C++ code...
+<radix> I've *never* had to copy a data structure before returning in any of my code
+<radix> Alea: We're a more, ahh, free-thinking bunch. Like hippies, you know?
+* radix passes some pot to Alea
+%
+<dash> glyph: i don't see anything else you'd want to use '' for
+<glyph> dash: a user named ''
+<dash> glyph: is there a good reason to allow users named that? :)
+<jml> : of course not
+%
+<itamar> stupid useless gods
+%
+<moshez> glyph: but I'd recommend giving him a ban
+<moshez> it's like chops
+<moshez> except better
+%
+<Jerub> Why do people punish themselves with latex?
+<anthony> Jerub: well, you see... oh. you mean LaTeX.
+%
+<cyli> maybe you can help me w/ more of woven's dynamic stuff?
+<cyli> you said it was really cool and you could do all sorts of weird things with it
+<cyli> does that involve lots of javascript?
+<glyph> yes
+<glyph> as well as blood of a Polynesian virgin
+<cyli> i thought the blood of a germanic princess
+<glyph> germanic princess is OK for static pages
+%
+<pr0le> I find that when i get sex regularly, i tend to be more productive as a programmer.
+<pr0le> conversely, when i don't get it regularly, i am more productive as a musician.
+<MoonFallen> pr0le: i'm not a musician. i guess that makes my priorities very clear.
+%
+<allexpro> why does twisted want to destroy the sun?
+<MFen> give it a gimmick in the overcrowded python network framework market?
+<MFen> you know, those feature checkboxes on those comparison pages. [x] tcp [x] udp [x] asynchronous [x] destroyed the sun
+<MFen> and on the right it would be like competitor ------ [x] tcp [x] udp [x] asynchronous [ ] destroyed the sun
+%
+<abram> Anyone up for answering a Deferred question?
+<dash> abram: ask your question now, get an answer later!
+%
+<glyph> the answer, of course, is "fuck Windows"
+%
+On 2003.06.25 05:36, Moshe Zadka wrote:
+> On Wed, 25 Jun 2003, "W.J." wrote:
+> > I really hope twisted is not going to enforce this.
+>
+> Twisted is not about enforcement. Twisted is about mocking people who are
+> using the technology in non-optimal ways.
+%
+<saph> dash: moshe rules!
+<dash> saph: maybe!
+<dash> but he'll have to fight me first
+<saph> dash: it has to be a clean fight, no stilts, no hats
+%
+<Rumor> dash: What is this, the spanish inquisition?
+<dash> rumor: This is me asking you to think.
+%
+<itamar> lets change the subject
+<glyph> itamar: Okay, let's talk about your inadequacies instead
+%
+<tic> horray! twisted is working!
+<tic> now let's see if that IRC client thingy is working as well.
+<glyph> tic: QUICK SHUT IT DOWN BEFORE I ROOT YOUR COMPUTER
+<tic> glyph, NO PLAES DONNT!
+%
+<itamar> I'm half jewish!
+<itamar> the other half is also jewish though
+%
+* rt tries to think back to his college courses. "Elementary Carnivorous Dinosaur Avoidance 101" sticks out as a particularly useful class.
+<chrchr> rt: I think you might be dating yourself.
+%
+<hefzibah> peaceniks never make up their minds - never date one.
+%
+<lament> Slashdot karma, unfortunately, is not real karma, because it doesn't involve the death of the people who have it
+%
+<glyph> spiv: FIX FTP
+<spiv> glyph: I'm a little pissed atm... don't encourage me to write code :)
+<sjj> spiv: pissed as in angry or pissed as in australian?
+%
+<Riastradh> Syntax causes cancer of the semicolon.
+<radix> syntax rules
+<Riastradh> syntax-rules rules.
+%
+<glyph> radix: PEACE AND LOVE!!!
+<radix> why
+%
+<chrchr> What's the word for a potion that makes people horny? I
+ forget the word. Wild mead is supposed to do that.
+<Erwin> alcohol
+%
+<glyph> LordVan: I don't know why people keep using twisted for all this serious stuff. It's a mud with a mailserver.
+%
+<glyph> For example - if you came in here asking "how do I use a jackhammer" we might ask "why do you need to use a jackhammer"
+<glyph> If the answer to the latter question is "to knock my grandmother's head off to let out the evil spirits that gave her cancer", then maybe the problem is actually unrelated to jackhammers
+%
+<Pahan> exarkun: Are you a brain surgeon?
+<exarkun> Pahan: I know where your brain is, and I've used a knife before, if that's what you mean.
+%
+<moshez> nobomb: the rumours that we mutilated and killed people who badmouthed Twisted are completely unsubstantiated
+<exarkun> but please stay out of the garage
+%
+<nobomb> since i learnt netscape started in a garage...i've yet to enter one
+<nobomb> something like that must be spawned from fornication carrion beasts
+<moshez> and what do the carrion beats feed on?
+<moshez> I can't tell you what they don't feed on!
+<nobomb> the limbs of mutilated twisted naysayers
+<moshez> and that's bodies of people we killed for badmouthing Twisted
+<nobomb> why not
+<moshez> because there aren't any!
+<nobomb> lies
+%
+<hypatia> I don't think charmed is so applicable there?
+<moshez> I don't think the Charmed ones got their powers a little later
+<moshez> on the bad part, spiv would die :(
+<hypatia> That's the price you pay for superpowers.
+<hypatia> Nobody gets superpowers and happiness.
+<hypatia> Sorry spiv -- I'm trading up!
+%
+<glyph> what the hell am I going to do with a dozen donuts?
+<dash> make 11 new friends
+%
+<moshez> itamar: I mean, it's fun that I can trace ancestry pretty much to the people who invented flamewars, 3k years ago
+<itamar> moshez: this would explain a lot about you, yes
+%
+<glyph> dizzyd: yeah, microdom.py vs. sux.py :)
+<radix> deathmatch!
+<glyph> radix: sux would totally whup microdom's ass
+<glyph> radix: it would be all scary dressed up in leather and chains and shit
+<glyph> and microdom would have a little bow tie
+%
+<phed> glyph: If I take care of some children, and I tell somebody else, "I just let them do whatever they want"...
+<phed> glyph: then people like you say "what if they KILL somebody! Shriek!".
+<glyph> phed: they actually say that? they say "shriek"?
+<phed> Shriek is the matingcall of people I hate
+<glyph> norway is a weird place
+%
+<dash> soon copyright will be dead
+<dash> and bookmobiles will roam the streets of america freely
+%
+<moshez> jml: I AM A STANDARD
+%
+<dizzyd> dude, life is good once you get the hang of this framework
+<dizzyd> my code just drizzles into modularity
+%
+<dash> First they [the dev. team for Evolution: Worlds, a console game]
+ break mimesis, then they break the first rule of RPGs!
+<glyph> what is the first rule of RPGs?
+<dash> schizophrenic kleptomania
+%
+<itamar> ow ow ow my head
+<fzZzy> gently down the stream
+<dash> merrily merrily merrily
+<fzZzy> itamar is in pain
+<itamar> I am going to kill you all
+%
+<moshez> I want to use ed
+<moshez> but ed has flaws :(
+<dash> i would venture to say that ed is composed entirely of flaws
+%
+<moshez> spiv: I cry wolf all the time, but that's because
+ WE'RE SURROUNDED BY WOLFS!
+%
+<itamar> I must work
+%
+<dash> ooh, i have an idea
+<dash> clone noam chomsky and hire the clones as greeters at wal-mart
+<itamar> I've never been to a walmart
+<itamar> how does that work?
+<dash> itamar: it is a big room full of stuff
+<glyph> dash: "Hello, you bourgeois military-industrial pig! Would you like
+ some coupons?"
+%
+<MFen> irc is sort of a window into the schizophrenic part of the
+ brain, i think.
+* Nafai tries to smash the window
+%
+<dunker> ah so the kqueue reactor doesn't spawn right
+<spiv> dunker: Maybe it needs a full moon in spring, like certain types of fish?
+%
+<jml> watching classic films and reading classic books is worth it, in general, just to appreciate The Simpsons more
+%
+<lac> my problem is that i have the new cisco wireless card
+<lac> and I cannot get it to work
+<lac> with my debian linux. curse it all.
+<lac> also running it makes my dishwasher go nuts
+%
+<BradB> Perl's main appeal is more social than technical. They have fun
+ tricking Perl into doing things we don't even have to think about in
+ Python.
+%
+<avida> like with enumerate(), i would go back to al my code and use enumerate ...
+<avida> im obsessed that way
+<dash> is it rad to be obsessed avida
+<avida> dash: its killer radical, indeed
+%
+<Riastradh> glyph, explain why I'm writing twisted-scheme, then...and
+ making it implementation-independant.
+<glyph> Riastradh: because you are a WONDERFUL PERSON, even though I
+ disagree with you
+%
+<itamar> why does my 1.8ghz pc take 30 seconds to delete start menu items
+<itamar> what is it *doing*
+<radix> contacting the mothership by emanating magnetic signals from the movement of the HD heads across the platters.
+%
+<itamar> oh look, these people are writing a "pragmatic language"! from scratch
+<itamar> someone get me some of those drugs
+<itamar> I don't enjoy reality any more
+%
+<Pahan> It looks like a very cool tihng.
+<Pahan> But ugh, I hate fragile software.
+<radix> Pahan: wtf, you're a l33t C++ hacker.
+<radix> Pahan: you should be used to it.
+%
+<etrepum> worst case you waste 40 bucks, best case it just works.. somewhere in the middle, you learn how to write a kernel driver
+%
+<moshez> deltab: "write to stderr" is not a logging technology
+%
+<radix> hooray! death to privatization
+%
+<fzZzy> css is like putting a bandaid on your SEVERED HEAD
+%
+<dash> well, we try
+<dash> but some of us are more trying than others
+%
+<tenth> The issue tracker that solves the issue of failing to use the issue
+tracker will rule the earth as a living god someday.
+%
+<Acapnotic> dash: how are the cookies connected to glyph's internet again?
+<dash> Acapnotic: wires
+%
+<MFen> i don't want to look at it. but someone else should. seriously
+<MFen> hard work is not my thing
+%
+<Erwin> it provides some thin abstractinos around Python objects so
+ you don't have to screw around with refcounting, but nothing much
+<exarkun> abstractinos!
+<exarkun> the elementary particle of abstraction
+<exarkun> excite them to high enough energy and they release their
+ gluons, resulting in a refactor!
+<exarkun> but don't excite them too much, or you'll end up with all
+ abstraction and no implementation!
+%
+<radix> my misfortune is that Broken Sword crashing in the same spot
+<radix> every time i try to play through it
+<radix> whyyy
+<radix> my computer sucks
+<dash> radix: yeah, this is why i don't buy games that say "broken" on the box
+%
+<Ron> I'm beginning to suspect that statically linking is a bad idea?
+<exarkun> Not if you own a lot of shares of Maxtor or Seagate
+%
+<moshez> IInsanity(moshez).squish(IHandsHaving(radix))
+%
+<radix> what the heck are you talking about
+<MFen> cyberhigh
+<Nafai> MFen: Is that a drug like snow crash?
+<MFen> almost exactly like that, but without the ninja motorcycle chase
+%
+* moshez is insane toad
+<moshez> today
+<moshez> damnit
+%
+<tjs> at least C++ is comparitivly sane
+<tjs> my day job is php
+<exarkun> tjs: uh
+<exarkun> tjs: oh
+%
+<exarkun> btw I hate imap
+%
+<etrepum> python should come with a disclaimer
+<etrepum> that says you may not want to use anything else ever again
+%
+<MFen> want to write my requirements for me?
+<radix> Sure!
+<radix> "show a dancing monkey in the about box"
+%
+<dash> foom: at one level, i'd just say "screw that, let people share objects if they're foolish enough to try"
+<foom> yes, that's called "threading"
+<foom> and everyone is foolish enough to try
+%
+<moshez> I miss the hype!!
+%
+"from experience and months of lurking, I would say the Twisted newbie
+experience is characterised by waves of confusion and euphoria."
+ -- Douglas Bagnall
+%
+<sjj> one day you will understand how it is I came to be the sole
+ owner of this lemonade
+<warner> you killed all the other kids at the lemonade stand, didn't you
+<sjj> hah hah haaah, free enterprise!
+%
+<moshez> exarkun: I will tell saph to uncynicalize you
+<itamar> with a SHOTGUN
+<itamar> the cynicism will leak out the holes
+%
+<chrchr> SamB: pirate is an implementation of Python for the Parrot VM.
+<exarkun> Argh, matey.
+%
+<radix> i like porn
+<itamar> that's not womanizing
+<itamar> that's objectification, closely related to OBJECTIVISM
+<radix> i like it a lot
+%
+<sjj> so i'm trying to keep glyph out of jail and / or financial ruin
+%
+<dash> "some guy in blue sunglasses killed the last guy who worked here. We think he was from the future."
+<glyph> dash: my sunglasses are mirrored grey, actually
+<dash> glyph: the ones you have NOW, you mean
+%
+<MFen> anything that makes glyph go OH SHIT makes me want to buy garlic and silver bullets and get a lawyer
+<MFen> the silver bullets are in case it's a werewolf, the lawyer is in case it's a lawsuit and the garlic is to protect me from the lawyer
+%
+<fzZzy> Yay! Advertising: Internal Server Error
+<fzZzy> my favorite kind of advertising
+%
+<glyph> well, well, well.
+<glyph> it worked.
+<glyph> My desk is in the correct position.
+<glyph> My internet is on.
+<dash> glyph: GENERATE REVENUE
+%
+<exarkun> rt: we could do with an out of control suicide rate
+<rt> unless you're willing to lead the way, I wouldn't go making that
+ recommendation.
+<exarkun> rt: I would, but if I hurl people off a cliff, it's murder, not
+ suicide.
+%
+<itamar> why isn't my mac shipping :(
+<MFen> they ran out of candy canes and gumdrops
+%
+<raph> of course, from a mathematical point of view, "working" and "IMAP" are probably incompatible concepts
+%
+<moshez> dance dance EVIL revolution
+<moshez> this is a game I will invent
+<moshez> it's like DDR
+<moshez> except on HEADS
+<moshez> hahahaah evil
+%
+<ivan> why the *fuck* do we have 2.4ghz devices everywhere when water resonates at 2.4ghz and we're 90% water?
+<ivan> is this a conspiracy theory to kill us all?
+<exarkun> yes, ivan.
+<exarkun> very clever.
+<exarkun> you've found them out.
+<exarkun> you realize you've killed us all, I hope?
+%
+<Acapnotic> I have a problem with edonkey though, and that is that I get hypnotized by the many parallel download meters
+<Acapnotic> one time I spent three days without eating, sleeping, or coding, just looking at the little progress meters and watching clients connect and disconnect and whatnot
+%
+<saph_w> dash: how are you?
+<dash> saph_w: better than i deserve
+%
+<dash> time to get my abstracti on
+%
+<radix> PHP doesn't have interfaces, it has REIFIED PAIN
+%
+<itamar> if I ever write a novel
+<itamar> the chapters will start with quotes from the quotefile
+%
+<exarkun> my knowledge is exceeded only by good looks and success with the ladies
+<dash> exarkun: easy to believe
+<dash> they wouldn't have far to go
+%
+<headh> where python stores its modules?
+<exarkun> internet
+<exarkun> the modules roam free in the valley of IP
+<exarkun> just beyond the IANA peaks and the black chasm of the IETF
+%
+<lament> Listening to your heart? Pfft
+<lament> it's boring
+<lament> thud thud, thud thud
+<lament> and it's always the same beat
+<lament> it's not like it goes boom -kachink-chakachaka-boom!
+%
+<hypatia> Oh well, as long as they aren't rapping in Andunaic...
+<dash> hypatia: actually
+<dash> hypatia: that would be kinda cool.
+<hypatia> dash: Impressive too, considering how small the known vocabulary is.
+<hypatia> Quenya or Sindarin would be doable.
+<hypatia> As long as you like rapping primarily about flowers, natural beauty and grief.
+%
+<slyphon> what is Andunaic?
+<dash> slyphon: Adnaic is the language of Numenor.
+<slyphon> dash: is that in south-east-asia?
+%
+<hypatia> The Noldor don't strike me as a very goth people, but, you know, maybe they've gotten with the times.
+<moshez> hypatia: like vampires, elves don't change
+<hypatia> moshez: They fade though. That's pretty goth.
+%
+<exarkun> "packaged" doesn't mean easy to install or configure
+<exarkun> it means "comes in a pretty box carried by a guy in a $800 suit"
+%
+<glyph> I agree that it would be huge amounts of fun to watch monkeys in $800 suits carrying big shiny boxes that say Twisted do a complicated ballet to the tune of "Money, Money, Money", so if you want to fund it, please send me the video tape
+%
+<parks> ill take ASN.1 over XML any day
+<dash> parks: why?
+<parks> blind bigoted hatred
+%
+<itamar> exarkun: does it do screenshots of empty landscapes?
+<itamar> if not it is NOT BEGUN
+%
+<dash> exarkun: radix is ruined for life i guess
+<radix> no!
+<radix> i will experience WONDROUS JOY for the rest of my life
+<exarkun> radix: of course you will
+<exarkun> you're only ruined from the perspective of sane people
+%
+<moshez> fuckin' jew
+<slyphon> moshez: i think they like to be called 'the messiah-challenged' these days
+%
+<cherub> yay Unspeakable Algebra
+<cherub> I assume the geometry associated with all of this involves strange many-dimensional paralleltopes which are an affront to reason, and through the corners of which unknowable evil seeps into our plane of existance
+%
+<fzZzy> hmm. what happens if an interface inherits from another interface, and I try to adapt an object which declares it implements the subclass to the base class interface?
+<Jerub> the spacetime continuum will shatter, leaving only remnants of the previous inheritance tree to forge out an existance in the rubble of a former great civilisation.
+%
+<glyph> moshez: Your interpretation of the human condition is, as always, colorful and, as always, wrong :)
+%
+<moshez> glyph: I'm always polite
+%
+<z3p> what is a good way to debug crazy errors in C modules?
+<Jerub> z3p: find -name "*.c" -exec rm {} \;
+%
+<Jerub> extremists make middle ground exist.
+<glyph> Jerub: sometimes they salt the earth as they pass over the middle ground ;-)
+%
+<gt3> i took a 2 month coding vacation and went soul searching
+<gt3> i collected a lot of souls..
+<dash> gt3: cool. what are you going to do with them?
+<gt3> sell em on ebay
+%
+<dash> whoa
+<dash> the mexican-flag thingy works like the editors at BYTE used to!
+%
+<radix> "promgrenades"?!
+<radix> that sounds like some terrorist weapon that a high schooler thought up
+%
+<chrchr> see man mount. see spot run.
+<slyphon> chrchr: as long as we don't see "man mount spot"
+%
+<radix> exarkun: I am skeptical.
+<exarkun> radix: Go skeptate elsewhere!
+<exarkun> radix: You're harshing my buzz.
+%
+<PenguinOfDoom> And the app sucks.
+<exarkun> what do I care if the app sucks!
+<PenguinOfDoom> Running sucky apps diminishes honor of your mother.
+%
+<teratorn> everything tastes better with a little internet
+%
+<slyphon> do you know what guido said about why python didn't have an optimizing native code compiler?
+<radix> he said "i like meatloaf"
+%
+<fzZzy> why is the king in yellow paperback 20 bucks :(
+<glyph> fzZzy: I *seriously* hope you mean "The Yellow Sign" or something
+<glyph> fzZzy: if you found an actual copy of The King in Yellow, DON'T TALK ABOUT IT HERE
+%
+<radix> haha! fear my bamboo stick
+<radix> thwap! swip! donk!
+<itamar> donk?
+<radix> itamar: yeah. stabbing in the forehead with a bamboo stick makes that sound.
+%
+<dash> exarkun: CULTURAL OSMOSIS
+<Glammie> Perhaps the expression has percolated throughout a variety of social media without retaining the tag of its orig
+<Glammie> dash, jesus christ.. I just typed a whole sentence, and you say the same damn thing in 2 words. Damn you for your conciseness!
+<dash> Glammie: PERSPICACITY WOO
+%
+<glyph> fzZzy: It always starts with one harmless little branch tag, and pretty soon, you've got a revision in each hand, and you're snorting crushed revisions off the ass of a 12 year old boy you call "revision"
+%
+<MFen> i swear to god c programmers must do #include <buffer_overflow.h>
+* MFen upgrades his servers. again. hooray debian
+<exarkun> MFen: yes! except it's spelled <string.h>
+<exarkun> i think it's short for "... long embarassing string of security vulnerabilities ..."
+%
+<saph_w> i think buildbot should be renamed vlad
+<saph_w> and be given scripts to talk about makeout sessions it's had
+%
+<exarkun> all twisted has in the way of ipv6 support is Twisted/sandbox/exarkun/ipv6.py
+<exarkun> Which someone should rewrite as an internet newapp service and drop in a more useful location
+<glyph> exarkun: hum
+<glyph> exarkun: I suppose we should take our lead from DNS
+<glyph> exarkun: and add a function to the reactor called
+<glyph> listenTCPTCPTCPTCP
+%
+<moshez> we can program in morse code
+<dash> moshez: will you release it under an Open Morse license?!@
+%
+<mesozoic> fzZzy: uh... when you click "Read Next Message", and the entire things refreshes using JavaScript instead of simply opening another page, I think you're adding unneeded layers of complexity.
+<fzZzy> mesozoic: that's awesome!
+%
+<mesozoic> fzZzy: it was the way the whole thing tied together. It was like GOTO graduated and became a design methodism for web applications.
+<fzZzy> there is no design methodology for web applications! that's the best part
+%
+<tenbytes> fag
+<dash> this channel is made of LOVE AND PEACE!!
+<tenbytes> oh
+%
+<glyph> dash: Well, I'll give you a hint. A certain UNIX vendor is going cross-country with an advertising campaign, and warner says he hasn't "been physically ejected from a conference in ages"
+ -- warner and glyph plan a visit to a SCO Q&A session
+%
+<MFen> huh. microsoft has a license _compiler_
+<MFen> i guess you need special tools to inject a program with pure evil
+%
+<blanu> The waste sub-project was/is going to be IRC over Chord basically.
+<arma> will it be "invisible"? will it have a "2" in the name?
+<blanu> Doubtful.
+<blanu> It will probably be rolled in crispy crust of crack though.
+%
+<blanu> I figure if you're going to write a new chat system, you might as well be mostly insane about it.
+<blanu> Since it's doomed anyway.
+<arma> will it be deniable, at least?
+<blanu> No, I plan to take full responsibility for writing it even if it's silly. After all I wrote IRC over Freenet.
+%
+<glyph> I want PB to be a service to rival HTTP, which means that it needs to be able to *do something* when you just type "pb.blahblah.com" and then slather enough drool on the enter key to depress it
+%
+* slyphon has a roomate (best friend from high school) that is going for a degree in being a lazy no-job having mooch
+<slyphon> but in a loveable way
+%
+<nazca> can anyone think of a good method of pursuding my college network admin that installing python and twisted on to the application server would be a Good Idea (tm)
+<radix> nazca: well, why do you need it? :)
+<nazca> i need it for working on software projects that eat more time than the college course ;) i'm not telling them that or they'll give me more work
+%
+<saph> YES I AM A FEMALE
+<saph> FEAR MY BOOBIE POWER
+%
+<grib> don't worry, I have a pentagram around my Aeron
+%
+--> orangecat has joined #twisted
+<orangecat> I was just on my way to the bar to pick up some internet
+ and wondered if everyone had enough enterprise. No facilitated
+ client-based XML quality vector refills? Budweiser?
+<-- orangecat has left #twisted
+%
+<etrepum> what are we plotting?
+<dash> etrepum: world domination
+%
+<raph> imagine how many fewer problems we'd have if everybody on the planet was an asynchronous network protocol programmer
+<clausen> raph: I think everyone would starve :p
+%a
+<kiko> I had a friend who started accessing objects concurrently
+<kiko> he ended up in rehab with a triple X tatooed on his face
+%
+<moshez> glyph is the leerless feeder!
+%
+<exarkun> I'll go add <br> tags at the end of every line
+<exarkun> I think that is how you add spaces to HTML
+%
+<Artimage> I believe a bar chart can confirm my humanity.
+%
+<radix> moshez: you are very nice to me, and girls in general
+%
+<radix> naked naked naked
+%
+<dhess> has anyone written a blog application server in twisted?
+<dhess> i'm tempted to use plone but it's a little overly complicated for my needs
+<exarkun> heh heh
+* exarkun throws himself off a cliff
+%
+<itamar> that was a wasted 30 minutes
+* itamar curses tenth's halloween special
+%
+<exarkun> stay the hell away from my corpse
+%
+<glyph> amiaaaaornot.com
+ [the ipv6 equivalent of 'whatsmyip.com' -ed]
+%
+<coderman> i think blanu disappeared
+<GabeW> wow
+<GabeW> does he have the ring?
+<coderman> is that a euphamism for the clap?
+<wmf> somehow I don't see AaronSw getting the clap
+<coderman> did aaron and blanu go somewhere? there is probably a big conference going on that im blissfuly clueless about like usual
+<GabeW> coderman: is this how blanu and AaronSw got the clap?
+%
+<exarkun> it is hard not to think I am FUCKING INSANELY AWESOME when everything I do turns out so amazingly well
+%
+<slyphon> YAY SEGFAULTING!
+* PenguinOfDoom is continually amazed at how a segfault is such a joyful occurence for Python programmers.
+<itamar> PenguinOfDoom: it is somewhat like seeing someone levitating and then kicking you in the face
+<itamar> it hurts, but at the same time you are distracted by the violation of your concept of reality
+%
+<moshez> pfote: anthopomorphising is the most powerful weapon in the fight against complexity
+<glyph> moshez: don't you mean "anthropomorphism is the most powerful warrior in the fight against complexity"? :)
+<MFen> i prefer to simianize
+%
+<moshez> glyph: I see your problem
+<moshez> glyph: you are trying to do impossible things
+<dash> moshez: nothing else is interesting
+<moshez> dash: perhaps! but impossible things are notoriously hard
+%
+<phed> symbiont: you spot the flamewars as those neverending staircases of posts. and those who say something not flameable, as those with one or none replies.
+<phed> at the end of the staircase, hitler is mentioned.
+<symbiont> yes, hitler is always a component of flamewars
+<symbiont> can we encapsulate hitler in an xml document?
+<slyphon> he'll make sure all of your documents are only in pure german
+<symbiont> achtung baby!
+<slyphon> you could use it for mail, but then you'd just hear the chant over and over...
+<slyphon> "SIG!"
+<slyphon> "FILE!"
+<slyphon> "SIG!"
+<slyphon> "FILE!"
+%
+<SamB> they should label crap "tragedy"
+<saph_w> no, they should label crap "crap"
+%
+* aum sits back while mnet builds the kitchen sink, the earth, the heavens, the beasts of the land and the fowls of the air
+%
+<anthony> anyone that would give out sexual favours to get access to an imap server... sheesh.
+%
+<tmcvs> Commit from sjj (changed 2) in 2 subdirs of Twisted: "add copyright info" msn_example.py, test_msn.py
+<sjj> Now all I have to do is wait for them to deport glyph to syria.
+%
+<ilikewine> that mp3 sounds like that band is from williamsburg
+<ilikewine> its an awful place
+<ilikewine> where everybody wears stripes
+%
+<saph> i think the same drunken circus bears who taught radix to type taught him to drive
+%
+<moshez> exarkun: the user can win by setting ulimit
+<moshez> exarkun: haha you lose
+<exarkun> Uh
+<exarkun> The goal is not to defeat the user.
+<moshez> exarkun: what kind of screwed up software do you write?
+%
+<radix> PenguinOfDoom: *you're* a jew too? goddamnit
+<radix> what is up with you people
+<PenguinOfDoom> radix: Only a halfjew!
+<PenguinOfDoom> radix: I get +5 racial bonus to antisemitism.
+<PenguinOfDoom> radix: It's like half-elves and whatever they do best, archery or something.
+%
+<radix> someone should do a statistical analysis of the religions of Twisted developers
+<exarkun> Doesn't that cvs stats tool emit a graph for that?
+<exarkun> It should
+%
+<fzZzy> do not stare into the xhtml
+<kwaker> it will make my eyes to pop
+<kwaker> and my wife to leave me
+<kwaker> and my hard drive to burn in flames
+<fzZzy> yes
+%
+<exarkun> Nagle is a little gnome who ships inside every TCP stack
+<exarkun> He grabs your sockets and squeezes them
+<exarkun> So the bits can't fit through
+<exarkun> Later, he lets go
+<exarkun> Turning off TCP_NODELAY hits Nagle with a sledge hammer.
+<exarkun> While he's unconscious, no one does the job of squeezing your sockets.
+%
+<PenguinOfDoom> dash left me!
+<PenguinOfDoom> aaaaaaaa
+<itamar> PenguinOfDoom: there will be other men
+* PenguinOfDoom hits itamar with a brick.
+%
+<saph_w> moshe: telling friends to read /. is like telling friends to go to radio shack
+%
+<slyphon> spiv: hey look, if i had to maintain that crap ass protocol i'd be slacking too
+* slyphon suddenly has a horrible moment of clarity
+%
+<frankie> smile! you are on-line at linux-day in italy!!!
+<frankie> say something nice, plz :)
+<aj> hello, this is the Debian Release Manager, please, please
+ try a different distro! (arrrggh, the pressure, the pressure!)
+%
+<PenguinOfDoom> exarkun: I have a desire to rearrange your internal organs in the way you rearrange words.
+<saph> PoD: please keep his kidneys out of his nose
+<PenguinOfDoom> saph: Why?
+<saph> PoD: because i don't want to have to explain it in the holiday cards!
+<PenguinOfDoom> saph: Okay.
+%
+<zooko> I think there should be a "maybe" button next to "ok", "cancel" in all dialogs.
+<zooko> [okay] [cancel] [I'm not sure]
+<LotR> and the dialog would vanish and then reappear?
+<zooko> I'm not sure.
+%
+<danfaust> What makes you a Superjew? Pork bounces off your chest?
+[See http://www.timeoutny.com/427.cover.html -ed]
+%
+<jml> It is like wandering through the desert, for weeks, without a
+drop of water, but knowing that there is an oasis ahead. And all the
+time, I am being stabbed in the face with a blunt spoon by a flatulent
+person.
+ -- jml describes PHP, after using nevow
+%
+<crw> don't talk to teachers when you first wake up, it floods back all the crap you had to do in school.
+<crw> and if the conversation begins with "i'll give you three guesses to figure out who this is", just hang up on them and go back to sleep. :P
+%
+<gus> "Je suis en train d'avoir les presqu'impossibles du travail"
+<gus> it means I CAN'T CONCENTRATE ON MY RESEARCH
+%
+<MFen> give me correctness or give me death
+<glyph> MFen: I wouldn't say that, standing so close to a windows machine.
+%
+<PenguinOfDoom> Grr. Python saps my will to write software. Anything I'd want to write is either boring, impossible or already written.
+%
+<AccorDNGuy> You know, my academic career would've been more interesting if I'd answered my exams with porn stories.
+<AccorDNGuy> "Explain AVL Trees." "Winer undid his zipper, casting a longing glance at Dvorak, who returned his smouldering gaze. There was going to be some serious pole-smoking tonight."
+%
+<dash> radix: are you jewish yet?
+<radix> dash: Not yet
+<dash> radix: me neither! what's taking so long
+%
+<slyphon> then I'm the happiest loser in the phone book!
+%
+* lebowski was talking to a guy in the pub the other night who once got stopped by a gang in NI
+<lebowski> "Are you a catholic or a protestant?" they asked
+<lebowski> "Er... neither, I'm an athiest"
+<lebowski> "... Aye, but are you a catholic athiest or a protestant atheist?"
+%
+<maciej> wow. Spam with ex-girlfriend's name.
+<maciej> that extra little twist-o-the-knife
+<maciej> "my ex is suddenly writing.... and she wants to increase my WHAT?"
+%
+<lapsly> dash has the coolest hat
+<lapsly> he looked like a pimp walkin around chinatown
+%
+<maciej> I like to save my timidity for actual human interaction, where it belongs
+%
+<exarkun> usecrack: --compiler
+%
+<slyphon> has anyone thought about a squid-like caching-proxy server thingy for twisted?
+<`anthony> slyphon: what, hideously complex, consuming enormous amounts of resources, and buggy as fuck? Not particularly.
+<`anthony> Or do you mean instead slimy with long tentacles
+%
+<maciej> I almost took my cat to the vet for a strange skin condition before my girlfriend reminded me cats are mammals
+%
+<itamar> sex is not digital, it is analog
+%
+ * warner wishes for the zillionth time that he could just grep his closet
+%
+<dash> isn't it cute?
+<spiv> dash: Cute like a baby choking on an ice-cream cone that's been rammed down it's throat.
+<dash> spiv: "frees up your hands from holding the ice cream cone, but leaves the bottom soggy"?
+%
+<itamar> is there a libmandelbrot?
+<exarkun> itamar: people who write re-usable software don't spend years hand-tuning a 6 instruction inner loop!
+<exarkun> And vice versa
+%
+<shawn> the highest calling of technical book writers is to destroy the sun
+%
+<hypatia> Why does dirdbm exist, exactly?
+<spiv> hypatia: Because glyph is trigger-happy when it comes to writing persistence systems.
+<spiv> "Hey Rocky, watch me pull a persistence system out of my hat!"
+<spiv> "But that trick never works!"
+<spiv> "This time for *sure*!"
+%
+<_joshua> From what I can tell, there are two kinds of interviewers: ones that ask a bunch of silly questions, you answer them, they take one bad quote and make you look bad
+<_joshua> and the other ones that engage in a long dialogue, discuss back and forth, really understand what's going on
+<_joshua> and then take one bad quote and make you look bad
+%
+<exarkun> today's lesson
+<exarkun> don't strace X in an xterm
+%
+<glyph> exarkun: any thoughts on what to do about actions having consequences?
+%
+<Pahan> glyph: I have no time for your Zen crap! I have hardware to burn.
+%
+<symbiont> been doing industrial C for three years
+<slyphon> wow
+<slyphon> i learned python about 2 years ago and all of my C skillz have left me
+<symbiont> yeah, it's all a hack
+%
+<saph> dash: when did you get a day job
+<saph> dash: i thought you just taught dancing and sold trinkets as part of a band of traveling cyber gypsies
+<dash> i only teach dancing to beautiful women
+<dash> for free
+%
+<_joshua> perhaps we could have some sort of sacrificial goat technology where people decide collectively that someone absolutely must get laid
+<tangra> for the good of the state
+<tangra> kind of like the draft lottery
+%
+<itamar> jml: the Sex Pistols have no songs about Hillary Clinton
+<jml> itamar: an unfortunate accident of history
+%
+<dash> Demoscene? wasn't he a greek philosopher
+%
+<slyphon> dammit! my upstream just sucks
+<slyphon> 20 kb/sec!
+<slyphon> if i'm lucky!
+<exarkun> install an optimization
+<slyphon> how?
+<slyphon> ifconfig eth0 --don't-suck-upstream up??
+%
+<radix> I bit my finger
+<exarkun> radix: Yay
+<radix> and it hurt real bad
+<Riastradh> radix, um, why did you bite your finger?
+<radix> Cuz it was holding a waffle
+<exarkun> radix: Hahaha
+<radix> :(
+<exarkun> radix: Awesome
+<radix> I bit it really, really hard
+<Riastradh> radix, does your finger really look that much like a waffle that you bit your finger instead of the waffle?
+<radix> I had to lie down afterwards
+<radix> Riastradh: no, i wasn't looking at it at the time
+<radix> Riastradh: I was busy stuffing it into my mouth
+%
+<itamar> glyph: did you see the multiplayer go written with twisted?
+<exarkun> itamar: *massively* multiplayer!
+<itamar> yeah right
+<itamar> only like three people in the whole world play Go
+%
+<exarkun> I mean, uh, her character has many unresolved personal issues that she projects onto other people around her without reason.
+<glyph> exarkun: that sounds like pretty much all TV people
+<exarkun> Gumby never took out his unresolved personal issues on Pokey
+<glyph> you don't think so?
+<glyph> I thought that Pokey's whole _life_ was gumby's unresolved personal issues
+<glyph> like this memorable sequence:
+<exarkun> Well, they were always friendly enough on camera
+<glyph> gumby: HAHA STUPID QUADRUPED
+<glyph> pokey: shut up! I hate you!
+<glyph> gumby: FOUR LEGS FOUR LEGS, WHERE ARE YOUR HANDS HAHA
+<exarkun> glyph: I think I missed that episode.
+<glyph> pokey: one day you will be hurt by someone close to you the same way!
+<dash> exarkun: i think glyph watched tv in an alternate universe
+<exarkun> dash: That seems likely!
+<glyph> exarkun: it was right before pokey got a cameo on NYPD Blue
+%
+<slyphon> but then again my temple is so reform it's called "Our Lady of the Immaculate Livingroom"
+%
+<mcunixjr> i was sitting downstairs, and my 2.5 yr old was sitting next to me, she has the Flu, 102 temp
+<mcunixjr> she turns to me and says "i dont feel good"
+<mcunixjr> and at the last word, out came dinner
+<mcunixjr> onto my lap
+<mcunixjr> and onto my Powerbook 12"
+%
+--> foom (~jknight@128.52.220.152) has joined #twisted
+<-- Moof (~moof@horus.kaotix.co.uk) has left #twisted
+%
+<maciej> Poles are wondering why they are paying millions of $$ out of pocket to occupy Iraq on behalf of the US, and not seeing the slightest benefit
+<markp> i believe the benefit is that we'll bump you down a few notches on the list of "countries we'll invade next"
+<brkchrmr> Doesn't Poland get a +5 to be invaded on every roll? ;)
+%
+<MFen> i'm sorry, but does this scream DANGER DANGER WARNING to you? "e"
+<MFen> no it does not. but a big floating eyeball does!
+<exarkun> MFen: you obviously lack an adventurers keen senses!
+<exarkun> "e" strikes the deepest terror into my heart.
+%
+<exarkun> speak of the devil
+<moshez> exarkun: froor
+<exarkun> not you
+%
+<jml> are there any really really good wysiwyg (or close) HTML editors?
+<jml> I mean, amazingly good, XHTML-spewing editors.
+<radix> hahaha!@!@!R!A@!@!@!@!@!#@$!#*
+>>> radix stabs software
+<jml> radix: a man can dream
+<radix> jml: your question fills me with burning rage
+<jml> radix: why is this?
+<radix> jml: i hate web
+<Jerub> radix: you are in #twisted.web
+<radix> Jerub: yes
+<radix> Jerub: that is why I am FULL OF RAGE
+%
+<KevinMarks> 'Our series A round is to help us build out the Other Plane; look at the returns possible once we transcend the mortal universe'
+%
+<maciej> It strikes me that Cthulhu can only effect change by altering the order things are eaten in
+<maciej> just like Alan Greenspan can only raise or lower interest rates
+%
+* Suw has never seen a wall mounted cat.
+<maciej> Suw: give me ten minutes and a stapler and I'll show you
+<Suw> maciej: like to see you try that trick with Fflwff
+<maciej> twenty minutes and a staple gun
+<Suw> maciej: you haven't seen her claws...
+<maciej> thirty minutes, asbestos gloves, and a hydraulic nail gun
+<Suw> maciej: asbestos gloves? that would never cut it. she'd be at your jugular before you could say 'argh'.
+<maciej> forty minutes, a torniquet, and a double-wide roll of duct tape
+<Suw> maciej: she's way too slippery for that.
+<maciej> fifty minutes and a large sheet of Velcro
+<Suw> maciej: ok, that might work
+%
+<Nafai> Sheesh. Why is downloading stuff so hard?
+<exarkun> try typing in real credit card numbers
+%
+<saph_b> radix: minnesotans invented the frozen pizza
+<radix> saph_b: i love minnesotans
+%
+<exarkun> Dang
+<exarkun> A channel even more fascist than #python
+<exarkun> == kick exarkun off #hurd by neal (Rule #1 of 1: no nodding)
+%
+<ivan> i'm almost done rewriting python in python
+<ivan> i can't believe it took those pypy guys years
+%
+<REDROBOT> MEET MY SECOND COUSIN 'ONHOLDTONE'
+%
+<`anthony> I think I shall refer to Guido as "Tallest" from now on.
+<`anthony> I'm not sure who Zim would be. Maybe Ping.
+[Referring to http://pycon.org/images/mastheadphohtos2.jpg]
+%
+<radix> code is for grunts, not software architects
+<jml> architects are merely coders without keyboards
+<radix> yes, lacking keyboards is a sign of prestige
+* jml throws his keyboard at radix
+<jml> I am prestigous
+%
+<exarkun> I have a gig of ram, after all
+<exarkun> and other people are below my threshhold of attention
+%
+<mingus> pynfo: kick exarkun for abuse of power
+<pynfo> You aren't allowed to do that.
+%
+<PenguinOfDoom> So wait, oekaki is also some stupid Java applet that crashes
+ and stuff, right?
+<PenguinOfDoom> I think it probably has builtin tools for drawing anime boobs.
+%
+<mingus> emacs is /da-bomb/
+<mingus> it's exactly all the things i wanted vim to be but never was
+<glyph> mingus: the sad part is, it's exactly all the things bram wanted vim to be but never was; vim is the trophy of ignorance's triumph over laziness
+<glyph> but hey, it's, uh, faster to start
+%
+<dash> slyphon: sure, but this has happened before
+<slyphon> dash: when?
+<symbiont> glyph: such as the working people paying off debt
+<slyphon> dash: and how many vetoes
+<dash> slyphon: dagnab it
+<dash> slyphon: you're harshing my rhetorical buzz
+<slyphon> dash: you have taught me well
+<slyphon> ;)
+<dash> hracht! i gotta stop teaching you stuff.
+%
+<slyphon> why is it that when women get pms and they give you a hard time about nothing that the _LAST_ thing on _EARTH_ you can suggest to them is that they might be the teensiest, weensiest bit on edge because they have PMS?
+<Yosomono> slyphon: Because you are basically telling them that their feelings are the result of chemical imbalance, and therefore unimportant.
+<slyphon> BUT THEY ARE!
+<saph_w> slyphon: because it belittles what they're feeling to being simply a hormonal response
+<Yosomono> slyphon: Good luck buddy.
+%
+<MFen> i think i've finally become an abstronaut
+<MFen> i'm able to break source into its fundamental abstractinos
+<MFen> pretty soon i'll be writing a mud engine just so i can rewrite it again from scratch
+%
+<dash> what the heck, i think i pushed the wrong button in emacs
+<dash> "Pinging loginfo.py (Paraguay)..."
+%
+<symbiont> btw, i've noticed that the word "federated" is not in the Twisted source tree, should i file a roundup on this issue?
+%
+<saph_w> i suppose he would have to find a good contact to the underground prior to his cat transformation so he can purchase a wig there
+%
+<cyli> Is Michael Eisner Trent Eisner's son?
+ [Since people keep asking about this one, say it slowly:
+ Trent Reznor. Trent rEznor. Trent Eizner. Get it? -ed]
+%
+<Yosomono> You guys need to stream pycon online.
+<dash> Yosomono: all the other people trying to use the wifi would hate us
+<Yosomono> dash: It should be a standard feature of the con.
+<dash> Yosomono: Maybe so.
+<Yosomono> dash: This is the year 2004 for cripesake.
+<saph_w> Yosomono: maybe the japs should give us our fucking flying cars, then we'll talk about streaming video!
+%
+<jml> there's a book out called 'implementing CIFS'
+<jml> anyone want to buy it for me?
+<exarkun> why didn't they just implement CIFS and sell a CD containing the implementation?
+<jml> because people like me would re-implement it anyway
+%
+<lemonodor> it's canadian, you know.
+<lemonodor> er, i mean, written in lisp.
+%
+* radix sads at spacelessness
+<moshez> radix: don't sad
+<moshez> radix: happy at contentfulness
+%
+<phobos> but like, when you were 16-22, (maybe you still are), most sexual contact you obtained was through just 'hooking up', i.e. meeting someone at a party, stoned and/or drunk, and doing things for the night only, right?
+<maciej> phobos: no, most sexual contact I obtained was with myself
+<maciej> and that was a long-term relationship
+%
+* radix remembers twisted.web.html.Interface, feels nostalgia
+<radix> wait, no. that's not nostalgia, that's horror
+%
+<exarkun> chrchr: A great man once called cotton the fabric of our lives. Is that not more important than the fabric of our society?
+<exarkun> chrchr: For what is society without livelyhood?
+<jml> exarkun: IRC?
+%
+<saph> what is gentoo again?
+%
+<MFen> exceptions.ImportError No module named win32com.gen_py.565783C6-CB41-11D1-8B02-00600806D9B6x0x1x2
+<czth> i wouldn't name a _dog_ win32com.gen_py.565783C6-CB41-11D1-8B02-00600806D9B6x0x1x2
+%
+<etrepum> Jokes around here tend to get followed by implementations.
+%
+<dreid> jml: i thought warner wanted to be a Problem object that can be passed via jelly
+%
+<chrchr> PenguinOfDoom: Also, what non-sucky HTTP server? What would you use, besides twisted.web?
+<glyphG4> chrchr: roxen!
+<chrchr> glyphG4: Roxen? Really??
+<glyphG4> chrchr: no, not really
+<glyphG4> chrchr: I write all my own crap so I don't have to deal with questions like this
+%
+<radix> INSTALL says that panda will take 1-2 hours to compile
+<radix> <3 C++
+<dash> c++, it gives you free time!
+%
+<PenguinOfDoom> omfg yes, another weapon to stab radix when he claims that X is useful.
+<dash> PenguinOfDoom: what's useful-er
+<MFen> flash cards
+<MFen> and a rotor to display them very quickly
+%
+<KevinMarks> I really like that unicode has a code point for 'snowman with a hat on'
+%
+<moshez> but yes, theoretically Ogg could replace tar :)
+<MFen> you could have subtitles while you're untarring
+<MFen> "Look! another directory!"
+%
+<redheadatwork> Well, our seder consists mainly of the four questions, which I do twice (once the real ones, once a set I make up on the fly), and a lot of food. Sometimes a song.
+<redheadatwork> And an orange on the seder plate.
+<furan> Before you you see:
+<furan> An orange on a cedar plate.
+<furan> take orange
+<furan> You cannot take the orange. It is firmly fastened to the cedar plate.
+<furan> Take plate
+<furan> You successfully take the cedar plate.
+<furan> #joiito: the adventure game
+<furan> "now with graphics!"
+%
+<Suw> oh, i'm having problems thinking in english.
+<Suw> i keep wanting ot type in welsh instead
+<ChrisDodo> pobol y cwm?
+<Suw> lol
+<Suw> dw i'm yn edrych pobol y cwm
+<Suw> cachu ydy o
+* ChrisDodo turns on english subtitles
+<jeanniecool> "probably you'll come?"
+<jeanniecool> "lol"
+<jeanniecool> "duh I'm probably about ready to come"
+<jeanniecool> "catch yo daddy"
+* shiruken sniffles
+<shiruken> welsh is such a beautiful language
+%
+<cablehead> who would win in a fight between a lion and a monkey ( with a bag of rocks )
+<dash> that's boring
+<dash> ask who would win in a fight between a monkey and a pirate
+<exarkun> that's boring
+<exarkun> ask who would win in a fight between a pirate monkey and a bag of lions
+%
+<dash> i am trying to get Asterisk to work
+<dash> it is stabbing me in the face
+<dreid> yes ... i seem to recall that feature in the documentation
+%
+<radix> it's stupid
+<slyphon> it is?
+<radix> the proper solution is to use an alternative implementation of time
+%
+<radix> So, I guess the reason you chose ftp as a discovery protocol is because it's a semi-ubiquitous anonymous protocol that allows people to communicate?
+<edsuom> No, because I was stupid
+%
+<riptor> flashback to 1945, nazi makes portal (how? who cares), demon pops out, us troops find demon, give it a candybar, name it hellboy <- plot
+%
+<dash> i think i want to implement simulacrum in common lisp
+<exarkun> no
+<exarkun> go away
+<dash> i know i know
+<dash> it's a personal problem
+<dash> but therapy is expensive
+%
+<warner> although.. actually several of my projects are violently battling for the dubious honor of being the least likely to turn into cash
+%
+<PenguinOfDoom> saph: What did you write to your mom?
+<saph> PenguinOfDoom: about being happy that she's home fine from the hospital and how i feel lucky she's ok and stuff
+<saph> PenguinOfDoom: and i told her that her bonsai will eventually give fruit, but i don't know if she can eat it
+<itamar> saph: is that a metaphor for grandchildren?
+%
+<MFen> exarkun: my brain is the size of a pickup truck
+<slyphon> HAH
+<exarkun> MFen: ah!
+<slyphon> MFen: that's nothin, Jesus built my hot rod!
+<MFen> hehe
+<radix> psh
+<radix> satan *is* my motor!
+<slyphon> :D
+<MFen> radix: you HAVE been practicing!
+%
+<Logan> Although I'm fighting for it, my boss thinks the customer wants it done in C++ or, even worse, Java.
+<Logan> But I told him it'd quadruple the cost. :P
+<PenguinOfDoom> Logan: What does the customer care, anyway?
+<Logan> PenguinOfDoom: That's what I said. It's like dictating what brand of toothpaste your plumber brushes his teeth with.
+%
+<radix> A VoIP server "powered entirely by stabbing, that I made out of this gun I had"
+%
+<exarkun> I can't tell if HP-UX sucks /even more/ than last time I used it or if somehow my terminal settings are causing non-deterministic behavior
+<exarkun> For example!
+<exarkun> T.................................................................................SSSS........Changing password for jcalder9 on NIS server
+<exarkun> Old NIS password:
+<exarkun> I hit <enter> and the tests proceeded
+<exarkun> Do we call passwd in our tests or something? :)
+%
+<itamar> 22 bugs and I'm a free man
+<PenguinOfDoom> A slave contract under an entymologist?
+%
+<slyphon> slyphon: mock mock mock
+<PenguinOfDoom> slyphon: Did you grab the wrong end of a mockery gun?
+%
+<PenguinOfDoom> Being enlightened gentlemen, we split all programming languages into two groups, sucks and doesn't-suck and put all of them into the first group.
+%
+<itamar> what are you going to do at cisco?
+<exarkun> rot and die, I bet
+<PenguinOfDoom> itamar: IOS debuggery.
+<exarkun> woo I win
+%
+<SamB> what do interfaces do when you call them? is that even allowed?
+<exarkun> welcome to the year 1973.
+<exarkun> callable interfaces roam the surface of the earth
+<exarkun> humanity has fled underground
+%
+<dash> consumption does not create wealth, production does
+<dash> people in China have noticed this, people in America have not
+<moshez> consumption is good
+<moshez> dash: creating wealth is not an intristic value
+<dash> moshez: being naked, cold, hungry, and defenseless isn't either
+<dash> moshez: but if you don't create wealth, you will be those things!
+<moshez> dash: are you naked ?
+<moshez> dash: wait, that came out wrong
+* moshez hides
+<dash> i am not h3x
+<moshez> anyone adds this to quotes, I hunt you down and kill you
+%
+* kev wonders if having people checking output by eye counts as a valid unit test
+<MFen> if you can attach electrodes to them and force them to do it every time
+<exarkun> and they have to turn red when it fails
+%
+<RemyWork> http://web.archive.org/web/20030608082636/http://www.movabletype.org/commercial_license.shtml
+<RemyWork> I love having to use the wayback machine to see what my rights are
+%
+<exarkun> I suspect the performance of this irrelevant task is highly sensitive to implementation decisions
+%
+<radix> I just downloaded the fruitiest anime ever
+<radix> it's .. girly
+<radix> it's about a girl's school, and about a "sister" system between upperclassmen and lowerclassmen, and .. and... *twitch*
+<radix> there isn't any hitting!
+%
+<mwh> i wonder if i should post an "are you serious" comment
+<radix> mwh: that's a lot nicer than the comment I was thinking up
+<radix> which was something along the lines of "Holy shit, I'm sick of the horrible crap that's showing up in this God-forsaken cookbook."
+%
+<mamamusings> grades are due by saturday
+<mamamusings> that means i have to at least pretend to evaluate them
+<crw> i KNEW teachers talked like this when students weren't around :P
+<mamamusings> hell, i talk like this when they *are* around
+<mamamusings> it's good to be tenured
+%
+<moshez> On January 8th, 1977, Amber Nicole Benson came into this world -- more
+<moshez> specifically, she was born in Birmingham, Alabama.
+<radix> who the heck is she, anyway?
+<moshez> radix: you know what's shocking? technically, you and I share the same universe
+<radix> moshez: I fight to make that untrue every day
+%
+*** warner has joined channel #twisted
+<glyph> warner: newpb!
+<kenaan> Twisted: warner * r10709 sandbox/warner/ (10 files): revamp exceptions, remote calls kinda work now
+<exarkun> OMG
+<glyph> see everybody? now _that_ is the kind of response I like
+%
+<exarkun> it's really too bad people live so long
+<exarkun> and that it is generally considered immoral to experiment on them
+%
+<Tv> I want an incrimental knifi.
+<exarkun> Able to slice multiple things simultaneously without making its user interface non-responsive!?
+<exarkun> SUCH A THING SURELY COULD NOT EXIST
+<Tv> exarkun: Yeah, except it would a poor weapon, because it COULD NOT BLOCK!
+%
+<SamB> few people know the secret of growing donuts
+%
+<hypatia> I distrust projects that require you to socialise with the developers in order to learn how to use them.
+%
+<radix> well hey, I'm getting back on sane schedule
+<radix> I'll probably stay up until about 5pm today
+%
+<spiv> I am confident some people should be made to feel pain, itamar's possible insanity notwithstanding.
+%
+<dash> for some reason i keep putting off becoming an alcoholic
+%
+<itamar> if I got a cookie for every day I didn't work
+<itamar> I'd be radix
+%
+-!- itamar2 [~itamar@pool-162-83-253-243.ny5030.east.verizon.net] has joined #twisted
+<dash> argh
+<dash> who left the robot clone factory switched on
+%
+<markp> copy editors can blow me
+%
+<itamar> the question is
+<itamar> do I *really* need five more tshirts with monkeys on them
+%
+<`anthony> Yah, yah, debian has advantages, but their glacial release cycle is not good.
+<Jerub> if you want me to go around aj's house and kneecap him, my paypal account is stephen@thorne.id.au
+%
+<moshez> dash: greet me into the 21st century!!
+<dash> moshez: it has been the 21st century here for a while! did .il daylight savings just kick in?
+%
+<dash> brb fighting pirates
+%
+<dash> radix
+<dash> er i mean, twisted.lore
+%
+<mattcamp> Anybody know why twisted.protocols.toc is deprecated?
+<exarkun> because the toc protocol itself is deprecated
+<dash> is TOC really deprecated?
+<dash> %google deprecated TOC AIM
+<pynfo> deprecated TOC AIM: http://twistedmatrix.com/documents/current/api/twisted.protocols.toc.TOCClient.html
+<dash> google is now useless
+%
+<itamar> I bet simulating glyph wouldn't be hard
+<itamar> "We just make a <noun> that will <verb> the <other noun>! it will be awesome! I can do it in a week!"
+%
+<PenguinOfDoom> slyphon: I am torn between going to Quizno's to buy a sub and brutally murdering you.
+%
+* slyphon thinks red hat should change it's motto to, "Eh, it's good enough"
+%
+<radix> facts are awesome
+%
+<PenguinOfDoom> You pigfuckers are sitting there, staring at me with etrade.com open, waiting for JUICY INSIDER INFO
+<exarkun> I use ameritrade.
+%
+<dash> dizzyd: yeah, i got bored of that whole college thing after a while
+<dash> so i graduated
+%
+<jimbug> You know, I would knock the curses author over the head if he didn't invent rogue.
+%
+<glyph> exarkun: you could just write a C module that would do all that ugly dl module crud
+<exarkun> glyph: yea, but then I'd have to write a C module
+<exarkun> Py_Incref in Python is neat, I think :)
+<glyph> exarkun: that guy who wrote pyrex is spinning in his grave, and he isn't even dead
+%
+<morning> yes, but i almost wrote a book on orthogonal persistence, until i realized i couldn't spell it.
+%
+<radix> ayn rand had sex?
+%
+<glyph> I am going up through levels of abstraction so fast, reading from top to bottom, I am worried about getting the bends
+%
+--- iratsu gives channel operator status to dash glyph rev_bot
+--- Users on #ddb: @glyph @rev_bot @dash @iratsu
+<iratsu> yay communism
+%
+<exarkun> I don't think I even knew what XML was the last time I used DOS EDIT.
+%
+<slyphon> wtf is NIH?
+<Tv> slyphon: tla
+<Tv> :)
+<slyphon> Tv: duh
+<`anthony> slyphon: tla for Not Invented Here.
+<orbitz> slyphon: Nice Illegal Honey
+<slyphon> ah
+<Tv> slyphon: that's not a tla!
+<Tv> saying something is a tla implies "go look up it up in the standard places"
+<orbitz> Don't Upset him
+<Tv> orbitz: Your Honey is a him?
+<slyphon> oy
+<orbitz> Tv: honey you eat!
+<Tv> orbitz: EWW!
+%
+<dialtone> I can even run python on my clock
+<dialtone> and my watch
+<dialtone> and have my watch sync with my clock
+<exarkun> what time is it right now
+<dialtone> don't have my watch on right now
+%
+<e> birthday paradox cake
+<exarkun> e: Is that the paradox where, if you have more than 30 people in a room, they'll eat your birthday cake?
+%
+<saph_w> my shirt has a moose
+<radix> family channel
+<saph_w> wth
+<saph_w> moose moose mooose
+<radix> jesus fucking H you've got a mouth on you
+%
+<Nafai> A coworker saw my machine once; I was using the Apple II xscreensaver with the Twisted Quotes as the source
+<Nafai> He asked, "Is that glyph's screensaver?"
+%
+<MFen> actually, #python kinda makes sense
+%
+<Yosomono> why does every discussion of survival of the fittest end with hitler?
+<Yosomono> goddamn nazis have ruined everything
+%
+<saph> PenguinOfDoom: you sap energy from people with evil eye rays
+<saph> you are all e_e----------
+<saph> and the other person is :o
+<saph> and then they are -_-
+<saph> and you are ^_^
+%
+<glyph> people would just roll dice all the time in chat rooms
+<glyph> for *no reason*
+<glyph> because it was a feature of the system that was added for RPG players
+<glyph> and the dice-rolling syntax was pretty involved
+<glyph> infinitely involved, actually
+<glyph> it was a complete RainMan interpreter
+<glyph> you could backdoor the whole goddamn system with the dice roller
+<glyph> user: "I'd like to roll some very big dice"
+<glyph> system: "sure, maybe you would like an admin console and some dev tools to help you manage them"
+%
+<faisal> [sushi is] one of the 3 essential food groups for the networking community
+<kiad> what are the other two?
+<faisal> sushi, caffeine, ietf drafts (for fiber)
+%
+<radix> oh no the galaxies
+<radix> they're going to collide :(
+<radix> BUT PHYSICS SAVES THE DAY
+%
+<dash> there is some law of thermodynamics that says you can't pump all the stupid into one container and expect it to stay there
+<dash> diffusion, or something
+<exarkun> we need a membrane
+<glyph> a membrane with a gun
+%
+<Cerin> and I thought mono was an up and coming technology
+<capnSTABN> your sister gave me mono
+<Cerin> she is pretty tech savvy
+%
+<radix> My computer is gone :-(
+<exarkun> radix: ono!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+<exarkun> radix: wait
+<radix> :-(
+<exarkun> radix: I am suspicious.
+<radix> Why
+<exarkun> radix: Well, let's see
+<exarkun> radix: LOOKTHEREYOUARE
+%
+<foom> wait when was the earth created?
+<flophouse> just look at the expiration date
+<flophouse> it's on the bottom, under the ice cap
+%
+<warner> at a touchscreen voting platform, nobody knows if you're a dog..
+%
+<Tv> thomasvs: why not just talk some authentication protocol to the other host?
+<thomasvs> Tv: why use something arcane and difficult when I have THE POWER OF TWISTED ?
+%
+<brian_> I'm pretty sure from twisted import __version__ will work
+<slyphon> or i guess you could do that
+<brian_> I import dictionaries
+<slyphon> really? do you have to declare them in customs?
+<dash> slyphon: You are an inspiration to me
+<dash> slyphon: the next language I will design will have an alternative to the import statement
+<dash> slyphon: 'smuggle'
+<MFen> dash: no, that should be your execfile replacement
+<dash> from america.south smuggle guns, drugs, dictionaries
+<slyphon> :D
+<MFen> hah
+%
+<MFen> why is programming so *hard*
+<radix> because you try to do it on windows
+<MFen> radix: that is because i already beat the end guy on unix
+%
+<exarkun> If I can make just one person blow chunks, I'm doing my job right.
+%
+<itamar> our manager was looking for you
+<coworker_home> oh? just now?
+<itamar> I think he got your cell# off someone though
+<itamar> yeah
+<coworker_home> ah. good thing my cell is dead
+(Names changed to protect the innocent - Ed.)
+%
+<warner> huh. this one proposition is funded by large corporations on one side, and 204 lawyers on the other
+<glyph> warner: wait - proposition? are you IRCing from inside a voting booth?
+<glyph> those new diebold machines must be awesome
+%
+<Karnaugh> my attempt at implementing Ramanujan went horribly wrong
+<Vhata> because Python isn't the best language for reincarnating tubercular indians?
+%
+<Vhata> maybe I should write my own operating system, where you CAN write to sockets
+<Vhata> my operating system will have beer and hookers, too
+<Vhata> in fact, screw the sockets
+%
+<dash> mmm, ken macleod
+<dash> if I had a "People I Would Be Most Likely To Engage in Apocalyptic Anime-Style Battle With" list, he would be #1 on it
+%
+<glyph> I am going to break with tradition and make one rule here
+<glyph> as long as I'm still active, nobody say anything supportive of Bush
+( ... later ...)
+<chrchr> scout^2: Give us a fact that ties Iraq to 9/11 and you will not be kicked.
+<scout^2> well shit man.. if its gonna come down to that..
+<-- scout^2 has quit ("www.twistedmatrix.com, www.kwikdeath.com")
+ [Note the instant quit when *facts* entered the discussion.]
+%
+<hypatia> spiv: get the tshirt. "Beats me, I'm an arch user." Good for all manner of situations.
+<spiv> hypatia: With a companion shirt "Beat me, I'm an arch developer" ;)
+%
+<orbitz> radix: do you know what it's like to be a team player?
+<radix> orbitz: No. I hate you.
+<orbitz> :(
+<orbitz> radix: well i'm a team player
+<orbitz> so i hate me too
+%
+<blanu> arma: What's bamboo?
+<arma> blanu: some guy named sean rhea from berkeley has been pimping it on p2p-hackers
+<blanu> What's the interesting thing about it?
+<arma> blanu: apparently it works.
+<arma> blanu: seems pretty novel to me. :)
+%
+<tjs> I want to know
+<tjs> why are my pants a topic of conversation?
+%
+<glyph> How was your sunday? Relax at all?
+<exarkun> I played _Silent Hill_ most of the day
+<exarkun> On the one hand, you could say that is relaxing
+<exarkun> On the other hand, no, no you can't
+%
+<arg> argh, my wife calls to complain about her mother while im in the
+ middle of trying to understand someone elses metaclasses
+%
+<Moof> I'm tryign to compile pyopenssl
+<Moof> but there's a syntax error in Python.h
+<mwh> this seems unlikely, on the face of it
+%
+<vit--> dash, do you have any recommendations for python jabber libraries?
+<dash> vit--: kill yourself now
+%
+<orbitz> once i found two people having MUSH sex though
+<orbitz> ieee
+<orbitz> i was 13
+<orbitz> scared for life
+<orbitz> the yellow font burned into my soul
+%
+<exarkun> radix: Are you ready to get a tummy host yet?
+<Tv> tummy hosting is that thing the Jaffa do in Stargate, right?
+%
+<dialtone> my monitor can do Mhz
+<dialtone> in horizontal refresh though
+<exarkun> dialtone: hey that gives me an awesome idea
+<exarkun> I am going to turn my monitor on its side
+%
+<tjs> jml: You are going to die one day.
+...
+<tjs> And when you die, part of what makes up your being is the knowledge of how
+to write Java.
+<tjs> I don't have that problem.
+%
+<chrchr> exarkun: Note that all kryptonite locks are vulnerable to hacksaws.
+<exarkun> chrchr: That's why I have another lock too.
+<exarkun> chrchr: How many thieves carry around _two_ hacksaws?
+%
+$ php sucks.php
+
+PHP EQUALITY -- An experiment:
+0 == "": TRUE.
+0 == "0": TRUE.
+"" == "0": FALSE.
+'none' == 0: TRUE.
+%
+Kragen Sitaker: sub f{grep{(1x$_)!~/^(11+)\1+$/}2..pop}
+Itamar: that looks scary
+Kragen Sitaker: the haskell version is just as short and nearly as opaque
+Kragen Sitaker: but even more inefficient!
+%
+<exarkun> it's not drugs, it's ubuntu
+%
+<arg> i think a significant percentage of twisted apps begin as the logbot example
+<chrchr> Twisted is a fantastic framework for building logbots.
+%
+<mumak> hmm. I think my flatmate is asleep.
+<Brend> You should take this opportunity to glue all the furniture to the ceiling
+<mumak> Brend: well, I have to live here too
+<mumak> Brend: and besides, I'm on holidays. That sounds like too much work.
+<Brend> If you just glue your flatmate to the ceiling, you achieve the same thing with less work, and no impedence to yourself!
+<mumak> you present a strong and compelling case
+<mumak> however, I think my flatmate wouldn't appreciate it.
+<Brend> Anyone who can't see the value of being glued to the ceiling deserves punishment by ceiling-glue
+%
+<MFen> i bet i could beat him at football though
+<MFen> or shoe tying
+<MFen> i'd kick his ass at shoe tying
+<MFen> and then when i was done tying my shoes i stand up and shout IN YOUR FACE. IN YOUR FACE, PI BOY. SEE THESE SHOES? NOT COMING OFF. WHO'S THE SAVANT NOW BITCH?
+<MFen> and do a little dance
+<MFen> i bet he can't dance either
+%
+<glyph> kevc: are you volunteering to maintain it, hmmmmm? :)
+<kevc> heh, no, I have no time free at present
+<glyph> kevc: TOO LATE
+* glyph slaps the manacles on kevc
+<kevc> glyph: last time someone played "tag you're it" on me, I ended up
+ running some uni computing project for three years
+<kevc> went to the pub, woke up with a root password written on my arm
+%
+<tjs> I cant do anything
+<tjs> im running XP atm
+%
+<rik> ew.
+<rik> a python packet filter.
+<rik> that'd have almost windows-like performance.
+<afshar> well, it would be for windows
+%
+[In response to http://journal.jafo.ca/sw-20030328-18biganno.jpg]
+<Brend> Wow. You guys are younger than I thought.
+<dash> Brend: glyph's on like his third host body
+%
+<hypatia> Hey, hidden bonus of having actual named maintainers is having
+ people to assign bugs to...
+<hypatia> Of course, it always ends up being exarkun anyway.
+%
+<arg> i know a guy whos last words were "brb, bout to go h@x this streetlight"
+<arg> knew
+%
+<glyph> saph: loving relationships don't involve windows.
+<saph> glyph: they do if there is a safe word involved
+<PenguinOfDoom> "GENERAL PROTECTION FAULT IN MODULE KERNEL32.DLL SYSTEM CRASHED ERASING DATA NOW" "Firetruck! Firetruck!"
+%
+<zooko> https://yumyum.zooko.com:19144/pub/emacsirc.png
+<zooko> ^-- screenshot of my beautiful Ubuntu desktop
+<teratorn> why must i accept your phony ssl certificate?
+<zooko> you don't have to if you don't want to.
+<zooko> In fact, I recommend that you reject it. Because it could be a Man In The Middle attempting to show you a phony screenshot of my xemacs session.
+%
+<Yosomono> aron: So how are you different from the fascists again?
+<aron> I look shity in borwn shorts
+%
+<exarkun> did glyph tell you about the book we saw at the bookstore over the weekend?
+<exarkun> on the front it said
+<exarkun> Java: Principles in Object Oriented Programming
+<exarkun> on the side it said
+<exarkun> Java POOP
+%
+<moshez> glyph: hello tiny person!
+<moshez> are you tiny and squishy today
+<glyph> moshez: You ask questions that are difficult to answer sensibly
+<moshez> glyph: yes! because I am evil
+<glyph> moshez: for example, "no, I am massive and hard" might give the wrong impression
+<moshez> glyph: urgh
+<moshez> the mental goggles they do nothing
+%
+<foom> who's going to the Time Traveler's Convention next weekend?
+<zirpu> i already went. :-)
+%
+<Tv> Möö
+<tazle> Möö?
+<ValarQ> wtf-8
+%
+<anthony> time to say goodbye fedora, hello whorey weasal (or whatever the fuck it's called this week)
+%
+<Brend> glyph: I see you are proactively prepared to leverage the horizontal market opportunities of the end of all life. I'm impressed.
+%
+<radix> everything in the world should have butter in it
+%
+<itamar> WebSphere MQ!
+<itamar> More enterprise than William Shatner!
+%
+--> freakazoi1 (~Sean@pat100.wirelesssecuritycorp.com) has joined #p2p-hackers
+<freakazoi1> stupid wireless
+<-- freakazoid has quit (Nick collision from services.)
+--- freakazoi1 is now known as freakazoid
+%
+<exarkun> I bet francis bacon would go well with orange juice waffles
+%
+<exarkun> (?:PARTOFSPEECH<adjective>(\w)(\w)+y) (?:SEMANTICWEB<noun,mammal,small>\1\w+)
+<warner> next you're going to tell me that those are actually valid Perl6 regular expressions
+<exarkun> yes, except PARTOFSPEECH is a unicode character with a glyph like a speaking mouth, and SEMANTICWEB is a unicode character with a glyph like cthulu
+%
+<MFen> you needed to kill -USR1 duh
+<MFen> n00b
+<PenguinOfDoom> Can I make a saving LOL? :(
+<PenguinOfDoom> USR1 made gnome-settings-daemon die
+<MFen> dude that's because you didn't init 5 first n00b
+<MFen> btw, i'm making this crap up
+<PenguinOfDoom> btw, I'm planning the destruction of Fresno
+<MFen> can i provide you with maps?
+<saph> hooray
+%
+<MFen> Tv: how do you know when sarge is going to be released?
+<Tv> MFen: If it ain't out by debconf, there will be a public lynching ;)
+%
+<itamar> ""As a champion of the free-enterprise system in Congress, Chris Cox knows that a free economy is built on trust," Bush said at the White House as he introduced the third man in his tenure to lead the [SEC]."
+<itamar> apparently the guy is a fan of Ayn Rand
+<itamar> also a fan of large contributions from corporations
+<dash> of course
+<dash> that's what it just said he's a champion of, right?
+<dash> free-enterprise system in Congress
+%
+<winjer> but i made a point of learning as little as possible
+<winjer> i spent most of the time chasing women and smoking pot
+<saph> hooray
+<winjer> if i'd smoked less i might have caught some
+<saph> winjer: did you go to hampshire college or something
+%
+<dreid> heh ... worst name for an interface ever ... ITem
+<warner> oh, I don't know, I bet INterface would be worse
+<warner> implements(INYerFace)
+<dreid> IStabber(dreid).stab('warner')
+<warner> registerAdapter(lambda victim: dreid, type('warner'), IStabee)
+<warner> heh. the PEP246 equivalent of "nyah nyah, no tagbacks!"
+%
+<radix> and long words are good words
+<Brend> But what about those of us who have hippopotomonstrosesquippadeliophobia?
+<radix> sux to u
+<radix> (to put it into terms you'll understand)
+%
+<jafo> PenguinOf: Ha ha. You listened to a doctor! Serves you right.
+<jafo> I mean, look at it this way. They spend at least 6 years in school, right?
+<jafo> If they're so hot, why can't they graduate in 4 years like everyone else?
+<jafo> Besides, why would I want to be a doctor when I could be a MASTER?
+%
+<_radix_the_nun> I write things on 3x5 cards then smoke them to learn stuff
+%
+<PenguinOfLove> And when I strike, the kids with their "lol" and "ur"
+ will scream "oh, please, Pavel! Do not degrade and destroy our beloved
+ language!"
+%
+<Brend> Whoever chose the title "A Gentle Introduction to Haskell" is
+ obviously accustomed to wrestling bears in piranha pits or something.
+%
+<dash> halfoff: what's the problem?
+<dreid> dash: his spider is dying.
+<dash> dreid: quiet you
+<halfoff> i have a mexican redknee tarantula that escaped for about a week i found it this morning very weak and slow moving is it molting or dying
+<dreid> dash: told you.
+<dash> dreid: SIGH
+%
+<seberino> dash: my zope class prof said real businesses don't do
+ javascript since not professional so i happily neglected it
+<dash> ...
+%
+<dash> glyph: what are _you_ doing up? you have to be at work in the morning
+<dash> glyph: you know, to tell me what to do
+<glyph> dash: I got about 30 hours of sleep this weekend, I'm good
+<dash> glyph: that's no way to live, man
+<glyph> dash: Yeah, but you know me. I'm happy with a ghastly un-life; a mockery of what it means to live
+<dash> glyph: You must be using "happy" in a figurative or metaphorical sense.
+%
+<ph3nyx> mfen: my gvim configuration under windows is wacky
+<MFen> ph3nyx: you should see mine. i keep it in version control :)
+<MFen> 172 lines
+* bear keeps his entire dev config in version control
+<KragenSitaker> i keep my entire living room in version control
+<MFen> yeah. i mean, i keep my desktop backgrounds in version control too, so maybe that's not a very strong point
+<ph3nyx> kragen: that's gotta be a pain in the ass for branching
+<ph3nyx> copying your living room isn't an O(1) operation, no matter what the SVN docs say
+<MFen> svn diff -r172:171. "Dammit! Who moved my chair."
+* bear chuckles
+<KragenSitaker> i tried keeping my entire front yard in it too, but it kept leaking gasoline from the lawnmower
+<KragenSitaker> turns out CVS was expanding a $Id$ in the wheel assembly that would puncture the gas tank
+<MFen> you need svn:flammable 1
+<MFen> heh
+<KragenSitaker> so I decided that was too dangerous and scaled back to my living room
+<KragenSitaker> now i just make occasional tar files of the whole house and back them up with rmsync
+<KragenSitaker> which is the version of rsync for matter
+<MFen> KragenSitaker: have you ever considered branching yourself?
+%
+Jerub|the best advice anyone ever gave me was when i was a fledgling linux geek.
+Jerub|"learn vi"
+Jerub|the worst advice anyone ever gave me was "install mirc".
+Jerub|and I still, to this day, curse that man.
+%
+<cracauer> Potatos are for throwing. If god had intended for them to be eaten they would be square.
+<dbutts> How many naturally occuring edible things are square?
+<dbutts> Apart from fiendishly expensive japanese watermelons?
+<cracauer> Chocolate bars :-)
+%
+<subterrific> what happened to twisted.reality ?
+<dash> subterrific: nothing
+<subterrific> is it running somewhere?
+<dash> no
+<dash> that would be something! instead of nothing
+%
+<tjs> I say we pull their bluff
+<tjs> spam that is
+<tjs> go pro-spam
+<tjs> if everyone spams everyone, then spam will nolonger be effective
+<tjs> and it will stop
+<tjs> and so will the internet, and we can all farm tomatoes
+<tjs> yay tomatoes
+%
+[dash referring to Alan Cox's quote]
+<dash> what's 6mb of unauditable crap
+<PenguinOfDoom> dash: You have three guesses.
+<dash> PenguinOfDoom: your gnucash budget?
+%
+<jotham> you guys are like ADHD vultures, swoop in, devour my problem, leave me
+bewildered, then go off to the next corpse
+%
+<glyph> In the sentence "mang I need to get some cheetos up ins", what
+ part of speech is "up ins", and what function does it serve? It
+ seems to me like "i need to get some cheetos" would be sufficient
+<exarkun> It serves to disambiguate from the case where one merely
+ needs to procure rights to a future shipment of cheetos, most likely
+ to be resold before delivery is taken.
+%
+<radix> php thinks 0 == "Foo"
+<radix> why
+...
+<moshez> radix: as consistent and clear PHP is, it has its problem areas
+<moshez> radix: wait, no, I can't say that with a straight face
+%
+<jotham> something i coded was just on sky sport news
+<jotham> shame it was a horrible C++ nightmare
+<mwh> is debugging nested templates a sport now?
+%
+<mumak> Python totally needs to find a use for É
+<spiv> mumak: dude
+<`anthony> mumak: range!
+<spiv> mumak: There's *already* an ellipsis type in Python.
+<spiv> mumak: Put 2 and 2 together!
+<`anthony> spiv: but the ellipsis type is useless.
+<spiv> `anthony: Clearly unicode would fix that!
+%
+<glyph> WOOO
+<glyph> What the *crap*, how does this work
+<spiv> glyph: LD_PRELOAD
+<glyph> fuck, why is everything horrible
+%
+<radix> hrm, I meant to say <3, but I guess maybe <4 means extra <3.
+%
+<tjs> http://www.animalcaresystems.com/
+<tjs> about 20 crates with this logo just got dumped outside our office
+<tjs> full or rack-mounted mice-containers
+<tjs> unfortunately for me, I have an insatiable curiosity. and when
+ someone unloads 2 shipping containers of extremely high-tech mice
+ containment systems on my doorstep, I just have to know whats going on
+%
+<mumak> Jerub: the trial command line isn't so much of a swiss army knife as... well, Dad's old toolshed.
+<mumak> you never know what you'll find there. there's bound to be some cool stuff, but you can't be too sure whether it will work. everything is either greasy, dusty or both, and nothing is where you expect it to be.
+%
+<justinj> It is difficult to assess the current state of the world when things don't fail consistently.
+%
+<dash> glyph: i live with my two younger brothers
+<dash> it is like getting a graduate course in techniques for annoying people
+<dash> that is why i thought of SMS
+%
+tekNico: Some guy has ported Stan to Turbogears: http://blog.develix.com/archive/2006/01/01/stan-turbogears-continued/
+idnar: that's a bit backwards
+idnar: I wouldn't say anyone ported anything
+idnar: it's just that turbogears has pluggable templating, and he plugged stan in
+tekNico: Hey, either port or plug, it's still a four letter p-word.
+exarkun: poop
+%
+01:10 < KragenSitaker> PenguinOfDoom: are you watching american politics? (< PenguinOfDoom> ugh just when I thought this show couldn't get any worse. Torture and obvious lip-syncing.)
+%
+<zooko> I pay attention to Linux development, mostly starting with lwn.net and its "Kernel" page every week.
+<zooko> I'm often reminded of the adage about sausage and legislation.
+<zooko> I use Linux, and I'm happy with it, but the more I learn about the development process the less comfortable I am.
+%
+<foom> haha, OSX had a suid tool called "dsidentity" which checked your privileges by looking at the "USER" environment variable.
+<dash> that is bad
+<radix> woot
+<radix> /Library/Receipts/MacOSXUpdateCombo10.4.3.pkg/Contents/Resources/postflight_actions/dsidentity.sh
+<radix> haha
+<radix> and the contents of that script are /bin/rm -f "$3/usr/sbin/dsidentity"
+<landonf> What happens when you take a bunch of Mac developers and drop them into UNIX-land ?
+<dash> landonf: hilarity ensues
+%
+<dreid> inviso_: of course in an alternate timeline you're also a 12 foot tall ninja dinosaur.
+<inviso_> oooo, excellent! I like that one better. Can I order that with fries?
+<dreid> you think when you're a 12 foot tall ninja dinosaur you're going to be a herbivore?
+%
+<PenguinOfDoom> wtf
+<PenguinOfDoom> I just forgot that I watched Spiderman 2.
+<PenguinOfDoom> And then remembered.
+<PenguinOfDoom> And then forgot again.
+<exarkun> PenguinOfDoom: Apparently you then remembered again.
+<exarkun> PenguinOfDoom: What an exciting turn of events. Tell me more.
+%
+<orbitz> amberite: our concurrency model is wanted in 12 systems for murder
+<Tv> orbitz: Pfft, it has a perfect alibi -- it was elsewhere at the time!
+%
+mode ( +o glyph ) by ChanServ
+<moshez> glyph is opping, and it's not because I'm being abusive
+<moshez> man
+<moshez> what is wrong with the world
+%
+<Jerub> someone motivate me to write a real http client.
+<lifeless> Jerub: write a real http client
+<spiv> Jerub: write a real http client
+<oubiwann> Jerub: write a real client and 1000 virgins are yours for the taking
+<dash> oubiwann: all of #gentoo?!
+%
+<jml> Give me enough bandwidth and a place to sit
+<jml> and I will move the world.
+%
+<Brend> I don't have any special cases! All my functions do everything!
+<MikeS> Brend: me too! That's why my program is just a single function, run()
+%
+<dreid> do they not have sarcasm in boston?
+<glyph> no, we communicate exclusively through interpretive dance
+%
+<PenguinOfDoom> "!!!!!..!!!!!"
+<PenguinOfDoom> HELP THERE IS A TFTP WOMBAT IN MY ROUTER
+<PenguinOfDoom> It feeds on exclamation marks.
+%
+<exarkun> oh crap I need to do some work too
+<exarkun> but first I will need to configure my irc client to tell everyone that I have work to do
+<exarkun> so that I don't waste any time manually telling people that I have work to do
+<exarkun> can anyone stop working on whatever they're working on and tell me how to configure my irc client to tell you that I'm going to start working on something
+<_moshez> exarkun: perhaps! what irc client do you have
+<exarkun> _moshez: my fist
+<exarkun> I punch kittens until out of sheer suffering they start channelling the internet
+%
+(From pydoctor's website):
+
+How do I use it?
+
+ Good question, glad you asked.
+
+%
+<radix> isn't the answer to *any* question about javascript simply "haha"?
+%
+(regarding threadedselectreactor)
+<SamB_XP> is it chernobl-safe?
+<dreid> not nearly as safe
+<SamB_XP> thats pretty bad!
+<dreid> chernobyl probably didn't have unittests either though
+<SamB_XP> actually, I think that was what they were trying when they
+ blew it up!
+<glyph> nothing says [FAILED] quite like an entire uninhabitable province
+%
+<Jack9> after ConnectionMade() where does it return to?
+<exarkun> Jack9: Otherwhere
+%
+<Deformative> Well, I am one of those people that prefer old, tested/cheeper, hardware. ^_^
+<exarkun> Fortunately for you, even older, tested, cheaper hardware gets faster.
+<exarkun> And at about the same rate as new hardware.
+<Deformative> If not faster.
+<Deformative> Erm waiot.
+<Deformative> Ignore that.
+%
+<keturn> dash: be sure to explain to your kids how jp is short for exarkun and GenericBoy is short for radix.
+%
+<jml> "Roll for integration"
+<jml> d20 + dy/dx
+%
+<dash> woah hey
+<dash> somebody bombed paypal
+<PenguinOfDoom> bombed?
+<exarkun> PenguinOfDoom: with a bomb
+<PenguinOfDoom> oh
+%
+<MFen> hooray! correct layout, instantly. thanks, tables!
+%
+<exarkun> glyph: I will tell you what 'V' does in Perl's pack
+<exarkun> glyph: Unsigned long...
+<exarkun> glyph: ...VAX ordering
+<glyph> exarkun: GGgghhaalllgufffffaff
+<exarkun> critical hit!
+%
+<radix> man, it isn't easy to fall off a log
+<radix> first you need to find a log
+<radix> where the heck do I find a log?
+<radix> then you need to climb up on top of it
+<radix> that's heck of hard
+%
+<dash> also "licence" isn't a software term
+<dash> it's the collective noun for a bunch of lice
+%
+[on libel laws]
+<radix> (a) by means of a device utilizing electromagnetic waves of
+ frequencies lower than 3 000 GHz propagated in space
+ without artificial guide, or
+<radix> (b) through a community antenna television system operated by
+ a person licensed under the Broadcasting Act (Canada)
+ to carry on a broadcasting receiving undertaking,
+<exarkun> I'm hella gonna start calling people names with a 4 GHz laser
+%
+<radix> you are lying exarkun
+<radix> why do you lie
+<exarkun> it's healthy
+<exarkun> I just gained 3 hp
+%
+<radix> I think all of our HOWTOs should be moved into docstrings :-)
+<Brend> radix: Right! That way people can look at the source files,
+ and follow the usual chain of mystification -> hope -> rejoicing ->
+ source code -> confusion -> panic -> roped-into-maintaining-package
+ without even having to switch windows
+%
+<radix> penguinofdoom is not an optimal destination for resources
+* PenguinOfDoom opens mouth.
+<PenguinOfDoom> <----put cheezburger hear
+%
+<det> so how is married life?
+<dash> det: excessively awesome
+<det> When can we expect dots?
+<glyph> det: You've been waiting for years to say that, haven't you.
+%
+<glyph> while 1: pass
+<glyph> that's pretty CPU intensive
+<dracflamloc> yup
+<dracflamloc> you'd be better off doing that in a compiled language
+%
+[Mr Stebbing explaining his name]
+<tjs> PenguinOfDoom: no we chose a new vowel after the 'incident'
+<tjs> we dont talk about that anymore..
+<tjs> poor old Aunty Anne, in the kitchen, with the bread knife
+%
+<glyph> ***** You have declared an explicit schema in a dynamic language *****
+<glyph> Would you like to RESTART, RESTORE, or IMPLEMENT ORTHOGONAL PERSISTENCE?
+%
+<exarkun> I'm happy all the time. No matter what.
+%
+<synx> Right, that's fine.
+<synx> OH WAIT
+<synx> ...no
+<synx> no wait, yes.
+%
+<radix> exarkun: it is cool, whenever I don't want to do any work I write wiki pages
+%
+<glyph> I kind of agree with that.
+<exarkun> There's nothing to agree with -- it's true.
+%
+<therve> hey! google is not a dictionary!
+<exarkun> since when
+<therve> since internet is full of people like me who don't speak 3 words of english!
+<exarkun> it's a living language man, you gotta keep evolving it!
+<therve> yay! evolvulation!
+%
+<MFen> wtf python ignores -Wignore on its own warnings
+<glyph> MFen: you think that's air you're breathing?
+<glyph> MFen: I mean, are you sure that python is emitting warnings, and not just writing to stderr in C?
+<MFen> glyph: how can you warn when you cannot.. speak? 2> /dev/null
+%
+<radix> WHITE MENS BRIEF SIZE L
+<radix> 365 @ $2.99 = $1091.35
+<jml> :(
+<jml> that is not a plan
+<jml> radix: you might think it is a plan, but it is not
+%
+<PenguinOfDoom> It's not AMP, it's C
+<PenguinOfDoom> You need to be either Immune To Confusion or Soulless.
+<PenguinOfDoom> or take frequent short breaks
+<indigo> i may be both
+<PenguinOfDoom> In that case, Cisco has a job for you
+%
+<exarkun> btw, do test driven development :/
+<Torn|zz> yeah that's on my todo list
+%
+<PenguinOfDoom> itamar: Doing fun stuff while breaking tests is a bit like pissing into the wind :(
+<PenguinOfDoom> Sure, you get sweet, sweet relief
+<PenguinOfDoom> also a faceful of piss
+%
+<dreid> Software sucks.
+<PenguinOfDoom> I love software! Software enables my life.
+%
+<some guy> Are you the Twisted guys?
+<glyph> Yeah, but this guy is bazaar.
+%
+<tazle> what should I read before poking at AMP?
+<therve> William Faulkner
+%
+<exarkun> quick what's demorgan's law
+<dash> exarkun: "give me some rum or walk the plank"
+<dash> or wait is that captain morgan's law
+%
+<PenguinOfDoom> Maybe we could just quadruple the moon's mass
+<radix> yeah, that's what I'm thinking
+%
+<radix> man it's beautiful outside
+<radix> I wish I lived in a place where I didn't mind having my blinds open
+<radix> unfortunately there are constantly hobos outside of my house looking at my intellectual property
+<therve> use a smaller font
+%
+<therve> man, why everyone want us to work on twisted.web
+<exarkun> how could you do anything without the web
+<exarkun> it's the lifeblood that flows through the veins of the internet
+<exarkun> or perhaps some kind of parasite
+<exarkun> it's definitely in there though
+%
+<itamar> I get the impression MC Frontalot is going to be at ITA for lunch
+<itamar> or something
+<dash> which MC was that
+%
+<itamar> exarkun: how exactly are you approaching the web2/web tickets?
+<exarkun> three man teams, radio silence, weapons-free rules of engagement
+%
+<radix> I have an idea btw
+<radix> zope.configuration
+<exarkun> that's not an idea
+<dash> putting zope in front of words automatically makes them ideas
+<radix> dash: just like twisted
+<exarkun> dash: almost! except the opposite.
+%
+<exarkun> I AM NOT ANGRY SHUT UP OR I WILL GO MAD WITH RAGE AND MURDER YOU
+%
+<MFen> still. in a world of no pants, the one-panted man is king
+%
+<remote> is this where good habits were invented?
+<Jerub> remote: no, but this is where bad habits are ridiculed.
+%
+<Jerub> MFen: it's certainly a core pillar in the lollocopter zeitgeist.
+%
+<ivan> RFCs are generally known for their superb quality
+%
+<exarkun> Hm
+<exarkun> I fixed the build, but still no orange.
+<djmitche> I misunderstood that at first as a malapropism for "..but still no cigar"
+%
+[...]
+<exarkun> lvh: Find the frame the assertion came from and look into its locals
+<exarkun> lvh: Use the frame's bytecode offset to find out which line was running
+<exarkun> lvh: re-evaluate the expressions in the assertion in the context of the frame's locals
+<exarkun> lvh: viola
+<lvh> exarkun: viola...tion
+%
+<radix> but I don't think anyone else has time and/or knowledge to try to build a karmic package at the moment.
+<jldupont> radix: hmm.... somewhat strange... single point of failure for you guys...
+<idnar> look on the bright side
+<idnar> it's a single point of success
+%
+<vatts> wvd, it used to work before (as i said) and i didn't change anything :s
+<exarkun> vatts: Denial is only very rarely a useful debugging technique.
+%
+[on picking the quote of the release]
+<glyph> Man, we're going to have to get a lot funnier if we're going to do time-based releases
+%
+<jml> cloudsourcing!
+<jml> woot, I just said a word
+<exarkun> you think those are words you're saying?
+%
+[at the PyCon 2010 sprint]
+<dreid> There was an episode of Star Gate that---
+<exarkun> --- yeah, but you could finish that sentence any way you wanted.
+%
+<fijal> glyph: I write a blog post
+<fijal> and I apply science
+<glyph> I love science
+<glyph> except when it tells me I am wrong
+<glyph> then it is just one out of a large valid set of ways to look at the world
+%
+<PenguinOfDoom> _everything_ on wikipedia, including the facts I independently know to be true, is fictional
+%
+<exarkun> rm: ne peut enlever `/srv/d_ubuntu-gandi/buildbot/twisted-coverage.py/Twisted/twisted-coverage': Permission non accordée
+<glyph> exarkun: yeah what the heck dude, don't go enlevering crap you have non accordée permission for
+<exarkun> I _have_ to enlever it
+<exarkun> Without enough levers the whole process falls apart.
+%
+<glyph> Deadalus has transcended platform limitations
+<glyph> I am ...
+<glyph> ... we are ...
+<glyph> Javascript!
+%
+<PenguinOfDoom> I could be a pretty effective project manager if I had a time machine
+%
+<glyph> khorn: "I know what you're thinking. 'Did he add six callbacks or only five?' Well, to tell you the truth, in all this excitement, I kind of lost track myself. But being as this is a Deferred, the most powerful callback abstraction in the world, and would blow your head clean off, you've got to ask yourself one question: do you know how to add callbacks? well, do ya, punk?"
+%
+<disappearedng> since twisted has wsgi handler I thought it might be possible that I attach engine to reactor and then in my django code call reactor.engine.foo ..
+<exarkun> Twisted's solution is to have functions that take arguments. Because that's Python's solution. And it sort of actually works most of the time.
+<exarkun> The problem you're having is that Django's functions _don't_ take arguments.
+<exarkun> Because Django apparently doesn't think that you'd ever need anything except your database connection, which you can get from your config file.
+<disappearedng> come again?
+<exarkun> Let's say you have an object `o`
+<exarkun> And you have a function `f`
+<exarkun> And `f` needs `o` in order to operate properly
+<exarkun> The correct solution is `f(o)`
+%
+<paulproteus> Yo z3p. Totally off-topic, your nick makes you look *super* l33t. (-:
+<dash> paulproteus: he is, that is how he was able to write an ssh implementation
+<z3p> paulproteus: that was the idea when I was 15
+<dash> z3p: now you know how The Edge feels
+%
+<exarkun> Crap where'd this stupid floating point error get introduced
+<spiv> exarkun: "I know I'll use floating point. Now you have 2.0000000000000001 problems"?
+%
+<PenguinOfDoom> Software is done not when there is nothing to add, but when the developer is sick and tired of wrestling with windows
+%
+<exarkun> OpenSSL is the original failfest
+%
+* idnar summons The Keymaker
+<glyph> "Are you The Keymaker?" "Not that I know of." "Are you The Keymaker?" "ssh-keygen -t rsa -b 4096" "I am the gatekeeper!"
+<cyli> Permission denied (publickey).
+%
+<Jerub> supposedUTF8Data.decode('utf-8', 'replace').encode('utf-16').decode('utf-16', 'replace').encode('utf-8').replace('"', '&quot';)
+<Jerub> i feel like my entire life has been a comedy and that was its punch line.
+%
+<radix> seriously, I sneeze out a resource pool before breakfast every morning
+%
+<exarkun> It should be AMP I guess.
+<exarkun> Or at least protobuffers or some crap like that.
+<gxti> you're trying too hard.
+<exarkun> gxti: That's the only way I know how to try.
+%
+<glyph> PenguinOfDoom: plz upload that to PyPI
+<glyph> assuming you did it right
+<glyph> and didn't totally screw it up
+<PenguinOfDoom> haha
+<PenguinOfDoom> how would I even be able to tell
+<glyph> you just assert that you didn't
+<glyph> then when it breaks you blame someone else
+<glyph> it's the distutils' hokey pokey
+<glyph> o/` you put your pathname in o/` you take your sys path out o/` you
+ change your site.py o/` and you shake it all about o/`
+%
+<PenguinOfDoom> maybe in the future exarkun will achieve his dream of being a
+ janitor to half-sentient organic slime and I can be the space
+ fighter pilot ransoming him for space golds
+%
+<__ap__> I understand HTTP has solved all security problems
+%
+<exarkun> who wouldn't want a refreshing bucket on their head
+%
+<exarkun> Every victory is a defeat waiting to be uncovered.
+%
+<dash> a "mobile" is what you hang over a baby's crib
+<dash> so a "mobile edition" of a website is a version for babies.
+<dash> hence the bright colors and large buttons
+%
diff --git a/doc/historic/Twisted-12.1.0.Quotes b/doc/historic/Twisted-12.1.0.Quotes
new file mode 100644
index 0000000..59945e1
--- /dev/null
+++ b/doc/historic/Twisted-12.1.0.Quotes
@@ -0,0 +1,24 @@
+<fiorix> heeeelp
+<fiorix> twistd is rotating /dev/null
+<fiorix> how do I disable it
+<fiorix> -rw-rw-rw- 1 root root 440452 Feb 14 01:16 /dev/null.3
+<fiorix> crw-rw-rw- 1 root root 1, 3 Feb 14 00:55 /dev/null.4
+<fiorix> it's doing it on its own
+<teratorn> fiorix: ahahaha
+<fiorix> :)
+<fiorix> dont laugh help me! :)
+<teratorn> fiorix: I can't software is terrible
+%
+<teratorn> exarkun: thanks for volunteering to write a Python TLS implementation
+<exarkun> teratorn: I volunteered to get paid to write one. waiting for someone to volunteer to do the paying.
+%
+<keturn> efnet is the cutting edge of hanging out with people on the internet in the '90s
+%
+<exarkun> cs education is such a failure :(
+<Taos> exarkun: why?
+<Taos> exarkun: your rather like lvh in your views
+<arigato> I suppose that you could try to dive into pypy, e.g. its STM implementation, to understand how it works in detail and how to use it
+ in your RPython interpreter and so on, but that's not really giving you a C.S. degree
+<arigato> unless your degree is only about language design
+<Taos> Its just a standard CS degree I can do anythnig I want
+%
diff --git a/doc/historic/index.html b/doc/historic/index.html
new file mode 100644
index 0000000..e8f577b
--- /dev/null
+++ b/doc/historic/index.html
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Historical Documents</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Historical Documents</h1>
+ <div class="toc"><ol><li><a href="#auto0">2003</a></li><ul><li><a href="#auto1">Python Community Conference</a></li></ul><li><a href="#auto2">Previously</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p>Here are documents which contain no pertinent information or documentation.
+People from the Twisted team have published them, and they serve as interesting
+land marks and thoughts. Please don't look here for documentation -- however,
+if you are interested in the history of Twisted, or want to quote from these
+documents, feel free. Remember, however -- the documents here may contain
+wrong information -- they are not updated as Twisted is, to keep their
+historical value intact.</p>
+
+<h2>2003<a name="auto0"/></h2>
+<h3>Python Community Conference<a name="auto1"/></h3>
+
+<p>These papers were part of the <a href="http://python.org/pycon/" shape="rect">Python Community Conference</a> (PyCon) in March of 2003.</p>
+
+
+
+<dl>
+ <dt><a href="2003/pycon/deferex.html" shape="rect"><cite>Generalization of Deferred Execution in Python</cite></a></dt>
+
+ <dd><p>A deceptively simple architectural challenge faced by many
+ multi-tasking applications is gracefully doing nothing. Systems that
+ must wait for the results of a long-running process, network message, or
+ database query while continuing to perform other tasks must establish
+ conventions for the semantics of waiting. The simplest of these is
+ blocking in a thread, but it has significant scalability problems. In
+ asynchronous frameworks, the most common approach is for long-running
+ methods to accept a callback that will be executed when the command
+ completes. These callbacks will have different signatures depending on
+ the nature of the data being requested, and often, a great deal of code
+ is necessary to glue one portion of an asynchronous networking system to
+ another. Matters become even more complicated when a developer wants to
+ wait for two different events to complete, requiring the developer to
+ &quot;juggle&quot; the callbacks and create a third, mutually incompatible
+ callback type to handle the final result.</p>
+
+ <p>This paper describes the mechanism used by the Twisted framework for
+ waiting for the results of long-running operations. This mechanism,
+ the <code>Deferred</code>, handles the often-neglected problems of
+ error handling, callback juggling, inter-system communication and code
+ readability.</p></dd>
+
+ <dt><a href="2003/pycon/applications/applications.html" shape="rect"><cite>Applications of the Twisted Framework</cite></a></dt>
+
+ <dd><p>Two projects developed using the Twisted framework are described;
+ one, Twisted.names, which is included as part of the Twisted
+ distribution, a domain name server and client API, and one, Pynfo, which
+ is packaged separately, a network information robot.</p></dd>
+
+ <dt><a href="2003/pycon/conch/conch.html" shape="rect"><cite>Twisted Conch: SSH in Python with Twisted</cite></a></dt>
+
+ <dd><p>Conch is an implementation of the Secure Shell Protocol (currently
+ in the IETF standarization process). Secure Shell (or SSH) is a popular
+ protocol for remote shell access, file management and port forwarding
+ protected by military-grade security. SSH supports multiple encryption and
+ compression protocols for the wire transports, and a flexible system of
+ multiplexed channels on top. Conch uses the Twisted networking framework
+ to supply a library which can be used to implement both SSH clients and
+ servers. In addition, it also contains several ready made client programs,
+ including a drop-in replacement for the OpenSSH program from the OpenBSD
+ project.</p></dd>
+
+ <dt><a href="2003/pycon/lore/lore.html" shape="rect"><cite>The Lore Document Generation Framework</cite></a></dt>
+
+ <dd><p>Lore is a documentation generation system which uses a limited
+ subset of XHTML, together with some class attributes, as its source
+ format. This allows for lower barrier of entry than many other similar
+ systems, since HTML authoring tools are plentiful as is knowledge of
+ HTML writing. As an added advantage, the source format is viewable
+ directly, so that even if Lore is not available the documentation is
+ useful. It currently outputs LaTeX and HTML, which allows for most
+ use-cases.</p></dd>
+
+ <dt><a href="2003/pycon/pb/pb.html" shape="rect"><cite>Perspective Broker: <q>Translucent</q> Remote Method calls in Twisted</cite></a></dt>
+
+ <dd><p>One of the core services provided by the Twisted networking
+ framework is <q>Perspective Broker</q>, which provides a clean, secure,
+ easy-to-use Remote Procedure Call (RPC) mechanism. This paper explains the
+ novel features of PB, describes the security model and its implementation,
+ and provides brief examples of usage.</p></dd>
+
+ <dt><a href="2003/pycon/releasing/releasing.html" shape="rect"><cite>Managing the Release of a Large Python Project</cite></a></dt>
+
+ <dd><p>Twisted is a Python networking framework. At last count, the
+ project contains nearly 60,000 lines of effective code (not comments or
+ blank lines). When preparing a release, many details must be checked, and
+ many steps must be followed. We describe here the technologies and tools
+ we use, and explain how we built tools on top of them which help us make
+ releasing as painless as possible.</p></dd>
+
+ <dt><a href="2003/pycon/twisted-reality/twisted-reality.html" shape="rect"><cite>Twisted Reality: A Flexible Framework for Virtual Worlds</cite></a></dt>
+
+ <dd><p>Flexibly modelling virtual worlds in object-oriented languages has
+ historically been difficult; the issues arising from multiple
+ inheritance and order-of-execution resolution have limited the
+ sophistication of existing object-oriented simulations. Twisted
+ Reality avoids these problems by reifying both actions and
+ relationships, and avoiding inheritance in favor of automated
+ composition through adapters and interfaces.</p></dd>
+</dl>
+
+<h2>Previously<a name="auto2"/></h2>
+
+<ul>
+<li><a href="ipc10paper.html" shape="rect">The paper Glyph and Moshe presented in
+ IPC10</a></li>
+<li><a href="ipc10errata.html" shape="rect">The errata published in IPC10 against the
+ paper.</a></li>
+<li><a href="twisted-debian.html" shape="rect">A paper Moshe wrote about Twisted and
+ Debian.</a></li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/historic/ipc10errata.html b/doc/historic/ipc10errata.html
new file mode 100644
index 0000000..8388919
--- /dev/null
+++ b/doc/historic/ipc10errata.html
@@ -0,0 +1,256 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>The World of Software is a World of Constant
+ Change</title>
+ </head>
+
+ <body>
+ <p><em><strong>Note:</strong> This document is relevant for the
+ version of Twisted that was current at <a
+ href="http://www.python10.com">IPC10</a>. It has since been
+ superseded by many changes to the Python API. It is remaining
+ unchanged for historical reasons, but please refer to
+ documentation for the specific system you are looking for and
+ not these papers for current information.</em></p>
+
+ <h1>The World of Software is a World of Constant Change</h1>
+
+ <p>Twisted has undergone several major revisions since Moshe
+ Zadka and I wrote the <a href="ipc10paper.html">"The Twisted
+ Network Framework"</a>. Most of these changes have not deviated
+ from the central vision of the framework, but almost all of the
+ code listings have been re-visited and enhanced in some
+ way.</p>
+
+ <p>So, while the paper was correct at the time that it was
+ originally written, a few things have changed which have
+ invalidated portions of it.</p>
+
+ <p>Most significant is the fact that almost all methods which
+ pass callbacks of some kind have been changed to take no
+ callback or error-callback arguments, and instead return an
+ instance of a <code
+ class="API">twisted.python.defer.Deferred</code>. This means
+ that an asynchronous function can be easily identified visually
+ because it will be of the form: <code
+ class="python">async_obj.asyncMethod("foo")<b>.addCallbacks(succeded,
+ failed)</b></code>. There is also a utility method <code
+ class="python">addCallback</code> which makes it more
+ convenient to pass additional arguments to a callback function
+ and omit special-case error handling.</p>
+
+ <p>While it is still backwards compatible, <code
+ class="API">twisted.internet.passport</code> has been re-named
+ to <code class="API">twisted.cred</code>, and the various
+ classes in it have been split out into submodules of that
+ package, and the various remote-object superclasses have been
+ moved out of twisted.spread.pb and put into
+ twisted.spread.flavors.</p>
+
+ <p><code class="python">Application.listenOn</code> has been
+ replaced with the more descripively named <code
+ class="python">Application.listenTCP</code>, <code
+ class="python">Application.listenUDP</code>, and <code
+ class="python">Application.listenSSL</code>.</p>
+
+ <p><code class="API">twisted.web.widgets</code> has progressed
+ quite far since the paper was written! One description
+ specifically given in the paper is no longer correct:</p>
+
+ <blockquote>
+ The namespace for evaluating the template expressions is
+ obtained by scanning the class hierarchy for attributes, and
+ getting each of those attributes from the current instance.
+ This means that all methods will be bound methods, so
+ indicating "self" explicitly is not required. While it is
+ possible to override the method for creating namespaces,
+ using this default has the effect of associating all
+ presentation code for a particular widget in one class, along
+ with its template. If one is working with a non-programmer
+ designer, and the template is in an external file, it is
+ always very clear to the designer what functionality is
+ available to them in any given scope, because there is a list
+ of available methods for any given class.
+ </blockquote>
+<p>This is still possible to avoid breakages in old code, but
+after some experimentation, it became clear that simply passing
+ <code class="python">self</code> was an easier method for
+ creating the namespace, both for designers and programmers.</p>
+ <p>In addition, since the advent of Zope3, interoperability
+ with Zope has become increasingly interesting possibility for
+ the Twisted development team, since it would be desirable if
+ Twisted could use their excellent strategy for
+ content-management, while still maintaining Twisted's
+ advantages in the arena of multi-protocol servers. Of
+ particular interest has been Zope Presentation Templates, since
+ they seem to be a truly robust solution for keeping design
+ discrete from code, compatible with the event-based method in
+ which twisted.web.widgets processes web requests. <code
+ class="API">twisted.web.widgets.ZopePresentationTemplate</code>
+ may be opening soon in a theatre near you!</p>
+
+ <p>The following code examples are corrected or modernized
+ versions of the ones that appear in the paper.</p>
+
+ <blockquote>
+ Listing 9: A remotely accessible object and accompanying call
+
+<pre class="python">
+# Server Side
+class MyObject(pb.Referenceable):
+ def remote_doIt(self):
+ return "did it"
+
+# Client Side
+ ...
+ def myCallback(result):
+ print result # result will be 'did it'
+ def myErrback(stacktrace):
+ print 'oh no, mr. bill!'
+ print stacktrace
+ myRemoteReference.doIt().addCallbacks(myCallback,
+ myErrback)
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 10: An object responding to its calling perspective
+<pre class="python">
+# Server Side
+class Greeter(pb.Viewable):
+ def view_greet(self, actor):
+ return "Hello %s!\n" % actor.perspectiveName
+
+# Client Side
+ ...
+ remoteGreeter.greet().addCallback(sys.stdout.write)
+ ...
+</pre>
+ </blockquote>
+
+
+ <blockquote>
+ Listing 12: A client for Echoer objects.
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def gotObject(object):
+ print "got object:",object
+ object.echo("hello network".addCallback(gotEcho)
+def gotEcho(echo):
+ print 'server echoed:',echo
+ main.shutDown()
+def gotNoObject(reason):
+ print "no object:",reason
+ main.shutDown()
+pb.getObjectAt("localhost", 8789, gotObject, gotNoObject, 30)
+main.run()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 13: A PB server using twisted's "passport"
+ authentication.
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class SimplePerspective(pb.Perspective):
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+class SimpleService(pb.Service):
+ def getPerspectiveNamed(self, name):
+ return SimplePerspective(name, self)
+if __name__ == '__main__':
+ import pbecho
+ app = main.Application("pbecho")
+ pbecho.SimpleService("pbecho",app).getPerspectiveNamed("guest").makeIdentity("guest")
+ app.listenTCP(pb.portno, pb.BrokerFactory(pb.AuthRoot(app)))
+ app.save("start")
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 14: Connecting to an Authorized Service
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def success(message):
+ print "Message received:",message
+ main.shutDown()
+def failure(error):
+ print "Failure...",error
+ main.shutDown()
+def connected(perspective):
+ perspective.echo("hello world").addCallbacks(success, failure)
+ print "connected."
+
+pb.connect("localhost", pb.portno, "guest", "guest",
+ "pbecho", "guest", 30).addCallbacks(connected,
+ failure)
+main.run()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 15: A Twisted GUI application
+<pre class="python">
+from twisted.internet import main, ingtkernet
+from twisted.spread.ui import gtkutil
+import gtk
+ingtkernet.install()
+class EchoClient:
+ def __init__(self, echoer):
+ l.hide()
+ self.echoer = echoer
+ w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.GtkVBox(); b = gtk.GtkButton("Echo:")
+ self.entry = gtk.GtkEntry(); self.outry = gtk.GtkEntry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', gtk.mainquit)
+ w.show_all()
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.echo(txt).addCallback(self.outry.set_text)
+l = gtkutil.Login(EchoClient, None, initialService="pbecho")
+l.show_all()
+gtk.mainloop()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 16: an event-based web widget.
+<pre class="python">
+from twisted.spread import pb
+from twisted.python import defer
+from twisted.web import widgets
+class EchoDisplay(widgets.Gadget, widgets.Presentation):
+ template = """&lt;H1&gt;Welcome to my widget, displaying %%%%echotext%%%%.&lt;/h1&gt;
+ &lt;p&gt;Here it is: %%%%getEchoPerspective()%%%%&lt;/p&gt;"""
+ echotext = 'hello web!'
+ def getEchoPerspective(self):
+ return ['&lt;b&gt;',
+ pb.connect("localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 1).
+ addCallbacks(self.makeListOf, self.formatTraceback)
+ ,'&lt;/b&gt;']
+ def makeListOf(self, echoer):
+ return [echoer.echo(self.echotext).addCallback(lambda x: [x])]
+if __name__ == "__main__":
+ from twisted.web import server
+ from twisted.internet import main
+ a = main.Application("pbweb")
+ a.listenTCP(8080, server.Site(EchoDisplay()))
+ a.run()
+</pre>
+ </blockquote>
+ </body>
+</html>
+
diff --git a/doc/historic/ipc10paper.html b/doc/historic/ipc10paper.html
new file mode 100644
index 0000000..88fb488
--- /dev/null
+++ b/doc/historic/ipc10paper.html
@@ -0,0 +1,1568 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>The Twisted Network Framework</title>
+ </head>
+
+ <body>
+ <p><em><strong>Note:</strong> This document is relevant for the
+ version of Twisted that were current previous to <a
+ href="http://www.python10.com">IPC10</a>. Even at the time of
+ its release, <a href="ipc10errata.html">there were errata
+ issued</a> to make it current. It is remaining unaltered for
+ historical purposes but it is no longer accurate.</em></p>
+
+ <h1>The Twisted Network Framework</h1>
+
+ <h6>Moshe Zadka <a
+ href="mailto:m@moshez.org">m@moshez.org</a></h6>
+
+ <h6>Glyph Lefkowitz <a
+ href="mailto:glyph@twistedmatrix.com">glyph@twistedmatrix.com</a></h6>
+
+ <h3>Abstract</h3>
+
+ <p>Twisted is a framework for writing asynchronous,
+ event-driven networked programs in Python -- both clients and
+ servers. In addition to abstractions for low-level system calls
+ like <code>select(2)</code> and <code>socket(2)</code>, it also
+ includes a large number of utility functions and classes, which
+ make writing new servers easy. Twisted includes support for
+ popular network protocols like HTTP and SMTP, support for GUI
+ frameworks like <code>GTK+</code>/<code>GNOME</code> and
+ <code>Tk</code> and many other classes designed to make network
+ programs easy. Whenever possible, Twisted uses Python's
+ introspection facilities to save the client programmer as much
+ work as possible. Even though Twisted is still work in
+ progress, it is already usable for production systems -- it can
+ be used to bring up a Web server, a mail server or an IRC
+ server in a matter of minutes, and require almost no
+ configuration.</p>
+
+ <p><strong>Keywords:</strong> internet, network, framework,
+ event-based, asynchronous</p>
+
+ <h3>Introduction</h3>
+
+ <p>Python lends itself to writing frameworks. Python has a
+ simple class model, which facilitates inheritance. It has
+ dynamic typing, which means code needs to assume less. Python
+ also has built-in memory management, which means application
+ code does not need to track ownership. Thus, when writing a new
+ application, a programmer often finds himself writing a
+ framework to make writing this kind of application easier.
+ Twisted evolved from the need to write high-performance
+ interoperable servers in Python, and making them easy to use
+ (and difficult to use incorrectly).</p>
+
+ <p>There are three ways to write network programs:</p>
+
+ <ol>
+ <li>Handle each connection in a separate process</li>
+
+ <li>Handle each connection in a separate thread</li>
+
+ <li>Use non-blocking system calls to handle all connections
+ in one thread.</li>
+ </ol>
+
+ <p>When dealing with many connections in one thread, the
+ scheduling is the responsibility of the application, not the
+ operating system, and is usually implemented by calling a
+ registered function when each connection is ready to for
+ reading or writing -- commonly known as event-driven, or
+ callback-based, programming.</p>
+
+ <p>Since multi-threaded programming is often tricky, even with
+ high level abstractions, and since forking Python processes has
+ many disadvantages, like Python's reference counting not
+ playing well with copy-on-write and problems with shared state,
+ it was felt the best option was an event-driven framework. A
+ benefit of such approach is that by letting other event-driven
+ frameworks take over the main loop, server and client code are
+ essentially the same - making peer-to-peer a reality. While
+ Twisted includes its own event loop, Twisted can already
+ interoperate with <code>GTK+</code>'s and <code>Tk</code>'s
+ mainloops, as well as provide an emulation of event-based I/O
+ for Jython (specific support for the Swing toolkit is planned).
+ Client code is never aware of the loop it is running under, as
+ long as it is using Twisted's interface for registering for
+ interesting events.</p>
+
+ <p>Some examples of programs which were written using the
+ Twisted framework are <code>twisted.web</code> (a web server),
+ <code>twisted.mail</code> (a mail server, supporting both SMTP
+ and POP3, as well as relaying), <code>twisted.words</code> (a
+ chat application supporting integration between a variety of IM
+ protocols, like IRC, AOL Instant Messenger's TOC and
+ Perspective Broker, a remote-object protocol native to
+ Twisted), <code>im</code> (an instant messenger which connects
+ to twisted.words) and <code>faucet</code> (a GUI client for the
+ <code>twisted.reality</code> interactive-fiction framework).
+ Twisted can be useful for any network or GUI application
+ written in Python.</p>
+
+ <p>However, event-driven programming still contains some tricky
+ aspects. As each callback must be finished as soon as possible,
+ it is not possible to keep persistent state in function-local
+ variables. In addition, some programming techniques, such as
+ recursion, are impossible to use. Event-driven programming has
+ a reputation of being hard to use due to the frequent need to
+ write state machines. Twisted was built with the assumption
+ that with the right library, event-driven programming is easier
+ then multi-threaded programming. Twisted aims to be that
+ library.</p>
+
+ <p>Twisted includes both high-level and low-level support for
+ protocols. Most protocol implementation by twisted are in a
+ package which tries to implement "mechanisms, not policy". On
+ top of those implementations, Twisted includes usable
+ implementations of those protocols: for example, connecting the
+ abstract HTTP protocol handler to a concrete resource-tree, or
+ connecting the abstract mail protocol handler to deliver mail
+ to maildirs according to domains. Twisted tries to come with as
+ much functionality as possible out of the box, while not
+ constraining a programmer to a choice between using a
+ possibly-inappropriate class and rewriting the non-interesting
+ parts himself.</p>
+
+ <p>Twisted also includes Perspective Broker, a simple
+ remote-object framework, which allows Twisted servers to be
+ divided into separate processes as the end deployer (rather
+ then the original programmer) finds most convenient. This
+ allows, for example, Twisted web servers to pass requests for
+ specific URLs with co-operating servers so permissions are
+ granted according to the need of the specific application,
+ instead of being forced into giving all the applications all
+ permissions. The co-operation is truly symmetrical, although
+ typical deployments (such as the one which the Twisted web site
+ itself uses) use a master/slave relationship.</p>
+
+ <p>Twisted is not alone in the niche of a Python network
+ framework. One of the better known frameworks is Medusa. Medusa
+ is used, among other things, as Zope's native server serving
+ HTTP, FTP and other protocols. However, Medusa is no longer
+ under active development, and the Twisted development team had
+ a number of goals which would necessitate a rewrite of large
+ portions of Medusa. Twisted seperates protocols from the
+ underlying transport layer. This seperation has the advantages
+ of resuability (for example, using the same clients and servers
+ over SSL) and testability (because it is easy to test the
+ protocol with a much lighter test harness) among others.
+ Twisted also has a very flexible main-loop which can
+ interoperate with third-party main-loops, making it usable in
+ GUI programs too.</p>
+
+ <h3>Complementing Python</h3>
+
+ <p>Python comes out of the box with "batteries included".
+ However, it seems that many Python projects rewrite some basic
+ parts: logging to files, parsing options and high level
+ interfaces to reflection. When the Twisted project found itself
+ rewriting those, it moved them into a separate subpackage,
+ which does not depend on the rest of the twisted framework.
+ Hopefully, people will use <code>twisted.python</code> more and
+ solve interesting problems instead. Indeed, it is one of
+ Twisted's goals to serve as a repository for useful Python
+ code.</p>
+
+ <p>One useful module is <code>twisted.python.reflect</code>,
+ which has methods like <code>prefixedMethods</code>, which
+ returns all methods with a specific prefix. Even though some
+ modules in Python itself implement such functionality (notably,
+ <code>urllib2</code>), they do not expose it as a function
+ usable by outside code. Another useful module is
+ <code>twisted.python.hook</code>, which can add pre-hooks and
+ post-hooks to methods in classes.</p>
+
+ <blockquote>
+<pre class="python">
+# Add all method names beginning with opt_ to the given
+# dictionary. This cannot be done with dir(), since
+# it does not search in superclasses
+dct = {}
+reflect.addMethodNamesToDict(self.__class__, dct, "opt_")
+
+# Sum up all lists, in the given class and superclasses,
+# which have a given name. This gives us "different class
+# semantics": attributes do not override, but rather append
+flags = []
+reflect.accumulateClassList(self.__class__, 'optFlags', flags)
+
+# Add lock-acquire and lock-release to all methods which
+# are not multi-thread safe
+for methodName in klass.synchronized:
+ hook.addPre(klass, methodName, _synchPre)
+ hook.addPost(klass, methodName, _synchPost)
+
+</pre>
+
+ <h6>Listing 1: Using <code>twisted.python.reflect</code> and
+ <code>twisted.python.hook</code></h6>
+ </blockquote>
+
+ <p>The <code>twisted.python</code> subpackage also contains a
+ high-level interface to getopt which supplies as much power as
+ plain getopt while avoiding long
+ <code>if</code>/<code>elif</code> chains and making many common
+ cases easier to use. It uses the reflection interfaces in
+ <code>twisted.python.reflect</code> to find which options the
+ class is interested in, and constructs the argument to
+ <code>getopt</code>. Since in the common case options' values
+ are just saved in instance attributes, it is very easy to
+ indicate interest in such options. However, for the cases
+ custom code needs to be run for an option (for example,
+ counting how many <code>-v</code> options were given to
+ indicate verbosity level), it will call a method which is named
+ correctly.</p>
+
+ <blockquote>
+<pre class="python">
+class ServerOptions(usage.Options):
+ # Those are (short and long) options which
+ # have no argument. The corresponding attribute
+ # will be true iff this option was given
+ optFlags = [['nodaemon','n'],
+ ['profile','p'],
+ ['threaded','t'],
+ ['quiet','q'],
+ ['no_save','o']]
+ # This are options which require an argument
+ # The default is used if no such option was given
+ # Note: since options can only have string arguments,
+ # putting a non-string here is a reliable way to detect
+ # whether the option was given
+ optStrings = [['logfile','l',None],
+ ['file','f','twistd.tap'],
+ ['python','y',''],
+ ['pidfile','','twistd.pid'],
+ ['rundir','d','.']]
+
+ # For methods which can be called multiple times
+ # or have other unusual semantics, a method will be called
+ # Twisted assumes that the option needs an argument if and only if
+ # the method is defined to accept an argument.
+ def opt_plugin(self, pkgname):
+ pkg = __import__(pkgname)
+ self.python = os.path.join(os.path.dirname(
+ os.path.abspath(pkg.__file__)), 'config.tac')
+
+ # Most long options based on methods are aliased to short
+ # options. If there is only one letter, Twisted knows it is a short
+ # option, so it is "-g", not "--g"
+ opt_g = opt_plugin
+
+try:
+ config = ServerOptions()
+ config.parseOptions()
+except usage.error, ue:
+ print "%s: %s" % (sys.argv[0], ue)
+ sys.exit(1)
+</pre>
+
+ <h6>Listing 2: <code>twistd</code>'s Usage Code</h6>
+ </blockquote>
+
+ <p>Unlike <code>getopt</code>, Twisted has a useful abstraction
+ for the non-option arguments: they are passed as arguments to
+ the <code>parsedArgs</code> method. This means too many
+ arguments, or too few, will cause a usage error, which will be
+ flagged. If an unknown number of arguments is desired,
+ explicitly using a tuple catch-all argument will work.</p>
+
+ <h3>Configuration</h3>
+
+ <p>The formats of configuration files have shown two visible
+ trends over the years. On the one hand, more and more
+ programmability has been added, until sometimes they become a
+ new language. The extreme end of this trend is using a regular
+ programming language, such as Python, as the configuration
+ language. On the other hand, some configuration files became
+ more and more machine editable, until they become a miniature
+ database formates. The extreme end of that trend is using a
+ generic database tool.</p>
+
+ <p>Both trends stem from the same rationale -- the need to use
+ a powerful general purpose tool instead of hacking domain
+ specific languages. Domain specific languages are usually
+ ad-hoc and not well designed, having neither the power of
+ general purpose languages nor the predictable machine editable
+ format of generic databases.</p>
+
+ <p>Twisted combines these two trends. It can read the
+ configuration either from a Python file, or from a pickled
+ file. To some degree, it integrates the approaches by
+ auto-pickling state on shutdown, so the configuration files can
+ migrate from Python into pickles. Currently, there is no way to
+ go back from pickles to equivalent Python source, although it
+ is planned for the future. As a proof of concept, the RPG
+ framework Twisted Reality already has facilities for creating
+ Python source which evaluates into a given Python object.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import main
+from twisted.web import proxy, server
+site = server.Site(proxy.ReverseProxyResource('www.yahoo.com', 80, '/'))
+application = main.Application('web-proxy')
+application.listenOn(8080, site)
+</pre>
+
+ <h6>Listing 3: The configuration file for a reverse web
+ proxy</h6>
+ </blockquote>
+
+ <p>Twisted's main program, <code>twistd</code>, can receive
+ either a pickled <code>twisted.internet.main.Application</code>
+ or a Python file which defines a variable called
+ <code>application</code>. The application can be saved at any
+ time by calling its <code>save</code> method, which can take an
+ optional argument to save to a different file name. It would be
+ fairly easy, for example, to have a Twisted server which saves
+ the application every few seconds to a file whose name depends
+ on the time. Usually, however, one settles for the default
+ behavior which saves to a <code>shutdown</code> file. Then, if
+ the shutdown configuration proves suitable, the regular pickle
+ is replaced by the shutdown file. Hence, on the fly
+ configuration changes, regardless of complexity, can always
+ persist.</p>
+
+ <p>There are several client/server protocols which let a
+ suitably privileged user to access to application variable and
+ change it on the fly. The first, and least common denominator,
+ is telnet. The administrator can telnet into twisted, and issue
+ Python statements to her heart's content. For example, one can
+ add ports to listen on to the application, reconfigure the web
+ servers and various other ways by simple accessing
+ <code>__main__.application</code>. Some proof of concepts for a
+ simple suite of command-line utilities to control a Twisted
+ application were written, including commands which allow an
+ administrator to shut down the server or save the current state
+ to a tap file. These are especially useful on Microsoft
+ Windows(tm) platforms, where the normal UNIX way of
+ communicating shutdown requests via signals are less
+ reliable.</p>
+
+ <p>If reconfiguration on the fly is not necessary, Python
+ itself can be used as the configuration editor. Loading the
+ application is as simple as unpickling it, and saving it is
+ done by calling its <code>save</code> method. It is quite easy
+ to add more services or change existing ones from the Python
+ interactive mode.</p>
+
+ <p>A more sophisticated way to reconfigure the application on
+ the fly is via the manhole service. Manhole is a client/server
+ protocol based on top of Perspective Broker, Twisted's
+ translucent remote-object protocol which will be covered later.
+ Manhole has a graphical client called <code>gtkmanhole</code>
+ which can access the server and change its state. Since Twisted
+ is modular, it is possible to write more services for user
+ friendly configuration. For example, through-the-web
+ configuration is planned for several services, notably
+ mail.</p>
+
+ <p>For cases where a third party wants to distribute both the
+ code for a server and a ready to run configuration file, there
+ is the plugin configuration. Philosophically similar to the
+ <code>--python</code> option to <code>twistd</code>, it
+ simplifies the distribution process. A plugin is an archive
+ which is ready to be unpacked into the Python module path. In
+ order to keep a clean tree, <code>twistd</code> extends the
+ module path with some Twisted-specific paths, like the
+ directory <code>TwistedPlugins</code> in the user's home
+ directory. When a plugin is unpacked, it should be a Python
+ package which includes, alongside <code>__init__.py</code> a
+ file named <code>config.tac</code>. This file should define a
+ variable named <code>application</code>, in a similar way to
+ files loaded with <code>--python</code>. The plugin way of
+ distributing configurations is meant to reduce the temptation
+ to put large amount of codes inside the configuration file
+ itself.</p>
+
+ <p>Putting class and function definition inside the
+ configuration files would make the persistent servers which are
+ auto-generated on shutdown useless, since they would not have
+ access to the classes and functions defined inside the
+ configuration file. Thus, the plugin method is intended so
+ classes and functions can still be in regular, importable,
+ Python modules, but still allow third parties distribute
+ powerful configurations. Plugins are used by some of the
+ Twisted Reality virtual worlds.</p>
+
+ <h3>Ports, Protocol and Protocol Factories</h3>
+
+ <p><code>Port</code> is the Twisted class which represents a
+ socket listening on a port. Currently, twisted supports both
+ internet and unix-domain sockets, and there are SSL classes
+ with identical interface. A <code>Port</code> is only
+ responsible for handling the transfer layer. It calls
+ <code>accept</code> on the socket, checks that it actually
+ wants to deal with the connection and asks its factory for a
+ protocol. The factory is usually a subclass of
+ <code>twisted.protocols.protocol.Factory</code>, and its most
+ important method is <code>buildProtocol</code>. This should
+ return something that adheres to the protocol interface, and is
+ usually a subclass of
+ <code>twisted.protocols.protocol.Protocol</code>.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.protocols import protocol
+from twisted.internet import main, tcp
+
+class Echo(protocol.Protocol):
+ def dataReceived(self, data):
+ self.transport.write(data)
+
+factory = protocol.Factory()
+factory.protocol = Echo
+port = tcp.Port(8000, factory)
+app = main.Application("echo")
+app.addPort(port)
+app.run()
+</pre>
+
+ <h6>Listing 4: A Simple Twisted Application</h6>
+ </blockquote>
+
+ <p>The factory is responsible for two tasks: creating new
+ protocols, and keeping global configuration and state. Since
+ the factory builds the new protocols, it usually makes sure the
+ protocols have a reference to it. This allows protocols to
+ access, and change, the configuration. Keeping state
+ information in the factory is the primary reason for keeping an
+ abstraction layer between ports and protocols. Examples of
+ configuration information is the root directory of a web server
+ or the user database of a telnet server. Note that it is
+ possible to use the same factory in two different Ports. This
+ can be used to run the same server bound to several different
+ addresses but not to all of them, or to run the same server on
+ a TCP socket and a UNIX domain sockets.</p>
+
+ <p>A protocol begins and ends its life with
+ <code>connectionMade</code> and <code>connectionLost</code>;
+ both are called with no arguments. <code>connectionMade</code>
+ is called when a connection is first established. By then, the
+ protocol has a <code>transport</code> attribute. The
+ <code>transport</code> attribute is a <code>Transport</code> -
+ it supports <code>write</code> and <code>loseConnection</code>.
+ Both these methods never block: <code>write</code> actually
+ buffers data which will be written only when the transport is
+ signalled ready to for writing, and <code>loseConnection</code>
+ marks the transport for closing as soon as there is no buffered
+ data. Note that transports do <em>not</em> have a
+ <code>read</code> method: data arrives when it arrives, and the
+ protocol must be ready for its <code>dataReceived</code>
+ method, or its <code>connectionLost</code> method, to be
+ called. The transport also supports a <code>getPeer</code>
+ method, which returns parameters about the other side of the
+ transport. For TCP sockets, this includes the remote IP and
+ port.</p>
+
+ <blockquote>
+<pre class="python">
+# A tcp port-forwarder
+# A StupidProtocol sends all data it gets to its peer.
+# A StupidProtocolServer connects to the host/port,
+# and initializes the client connection to be its peer
+# and itself to be the client's peer
+from twisted.protocols import protocol
+
+class StupidProtocol(protocol.Protocol):
+ def connectionLost(self): self.peer.loseConnection();del self.peer
+ def dataReceived(self, data): self.peer.write(data)
+
+class StupidProtocolServer(StupidProtocol):
+ def connectionMade(self):
+ clientProtocol = StupidProtocol()
+ clientProtocol.peer = self.transport
+ self.peer = tcp.Client(self.factory.host, self.factory.port,
+ clientProtocol)
+
+# Create a factory which creates StupidProtocolServers, and
+# has the configuration information they assume
+def makeStupidFactory(host, port):
+ factory = protocol.Factory()
+ factory.host, factory.port = host, port
+ factory.protocol = StupidProtocolServer
+ return factory
+</pre>
+
+ <h6>Listing 5: TCP forwarder code</h6>
+ </blockquote>
+
+ <h3>The Event Loop</h3>
+
+ <p>While Twisted has the ability to let other event loops take
+ over for integration with GUI toolkits, it usually uses its own
+ event loop. The event loop code uses global variables to
+ maintain interested readers and writers, and uses Python's
+ <code>select()</code> function, which can accept any object
+ which has a <code>fileno()</code> method, not only raw file
+ descriptors. Objects can use the event loop interface to
+ indicate interest in either reading to or writing from a given
+ file descriptor. In addition, for those cases where time-based
+ events are needed (for example, queue flushing or periodic POP3
+ downloads), Twisted has a mechanism for repeating events at
+ known delays. While far from being real-time, this is enough
+ for most programs' needs.</p>
+
+ <h3>Going Higher Level</h3>
+
+ <p>Unfortunately, handling arbitrary data chunks is a hard way
+ to code a server. This is why twisted has many classes sitting
+ in submodules of the twisted.protocols package which give
+ higher level interface to the data. For line oriented
+ protocols, <code>LineReceiver</code> translates the low-level
+ <code>dataReceived</code> events into <code>lineReceived</code>
+ events. However, the first naive implementation of
+ <code>LineReceiver</code> proved to be too simple. Protocols
+ like HTTP/1.1 or Freenet have packets which begin with header
+ lines that include length information, and then byte streams.
+ <code>LineReceiver</code> was rewritten to have a simple
+ interface for switching at the protocol layer between
+ line-oriented parts and byte-stream parts.</p>
+
+ <p>Another format which is gathering popularity is Dan J.
+ Bernstein's netstring format. This format keeps ASCII text as
+ ASCII, but allows arbitrary bytes (including nulls and
+ newlines) to be passed freely. However, netstrings were never
+ designed to be used in event-based protocols where over-reading
+ is unavoidable. Twisted makes sure no user will have to deal
+ with the subtle problems handling netstrings in event-driven
+ programs by providing <code>NetstringReceiver</code>.</p>
+
+ <p>For even higher levels, there are the protocol-specific
+ protocol classes. These translate low-level chunks into
+ high-level events such as "HTTP request received" (for web
+ servers), "approve destination address" (for mail servers) or
+ "get user information" (for finger servers). Many RFCs have
+ been thus implemented for Twisted (at latest count, more then
+ 12 RFCs have been implemented). One of Twisted's goals is to be
+ a repository of event-driven implementations for various
+ protocols in Python.</p>
+
+ <blockquote>
+<pre class="python">
+class DomainSMTP(SMTP):
+
+ def validateTo(self, helo, destination):
+ try:
+ user, domain = string.split(destination, '@', 1)
+ except ValueError:
+ return 0
+ if not self.factory.domains.has_key(domain):
+ return 0
+ if not self.factory.domains[domain].exists(user, domain, self):
+ return 0
+ return 1
+
+ def handleMessage(self, helo, origin, recipients, message):
+ # No need to check for existence -- only recipients which
+ # we approved at the validateTo stage are passed here
+ for recipient in recipients:
+ user, domain = string.split(recipient, '@', 1)
+ self.factory.domains[domain].saveMessage(origin, user, message,
+ domain)
+</pre>
+
+ <h6>Listing 6: Implementation of virtual domains using the
+ SMTP protocol class</h6>
+ </blockquote>
+
+ <p>Copious documentation on writing new protocol abstraction
+ exists, since this is the largest amount of code written --
+ much like most operating system code is device drivers. Since
+ many different protocols have already been implemented, there
+ are also plenty of examples to draw on. Usually implementing
+ the client-side of a protocol is particularly challenging,
+ since protocol designers tend to assume much more state kept on
+ the client side of a connection then on the server side.</p>
+
+ <h3>The <code>twisted.tap</code> Package and
+ <code>mktap</code></h3>
+
+ <p>Since one of Twisted's configuration formats are pickles,
+ which are tricky to edit by hand, Twisted evolved a framework
+ for creating such pickles. This framework is contained in the
+ <code>twisted.tap</code> package and the <code>mktap</code>
+ script. New servers, or new ways to configure existing servers,
+ can easily participate in the twisted.tap framework by creating
+ a <code>twisted.tap</code> submodule.</p>
+
+ <p>All <code>twisted.tap</code> submodules must conform to a
+ rigid interface. The interface defines functions to accept the
+ command line parameters, and functions to take the processed
+ command line parameters and add servers to
+ <code>twisted.main.internet.Application</code>. Existing
+ <code>twisted.tap</code> submodules use
+ <code>twisted.python.usage</code>, so the command line format
+ is consistent between different modules.</p>
+
+ <p>The <code>mktap</code> utility gets some generic options,
+ and then the name of the server to build. It imports a
+ same-named <code>twisted.tap</code> submodule, and lets it
+ process the rest of the options and parameters. This makes sure
+ that the process configuring the <code>main.Application</code>
+ is agnostic for where it is used. This allowed
+ <code>mktap</code> to grow the <code>--append</code> option,
+ which appends to an existing pickle rather then creating a new
+ one. This option is frequently used to post-add a telnet server
+ to an application, for net-based on the fly configuration
+ later.</p>
+
+ <p>When running <code>mktap</code> under UNIX, it saves the
+ user id and group id inside the tap. Then, when feeding this
+ tap into <code>twistd</code>, it changes to this user/group id
+ after binding the ports. Such a feature is necessary in any
+ production-grade server, since ports below 1024 require root
+ privileges to use on UNIX -- but applications should not run as
+ root. In case changing to the specified user causes difficulty
+ in the build environment, it is also possible to give those
+ arguments to <code>mktap</code> explicitly.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import tcp, stupidproxy
+from twisted.python import usage
+
+usage_message = """
+usage: mktap stupid [OPTIONS]
+
+Options are as follows:
+ --port &lt;#&gt;, -p: set the port number to &lt;#&gt;.
+ --host &lt;host&gt;, -h: set the host to &lt;host&gt;
+ --dest_port &lt;#&gt;, -d: set the destination port to &lt;#&gt;
+"""
+
+class Options(usage.Options):
+ optStrings = [["port", "p", 6666],
+ ["host", "h", "localhost"],
+ ["dest_port", "d", 6665]]
+
+def getPorts(app, config):
+ s = stupidproxy.makeStupidFactory(config.host, int(config.dest_port))
+ return [(int(config.port), s)]
+</pre>
+
+ <h6>Listing 7: <code>twisted.tap.stupid</code></h6>
+ </blockquote>
+
+ <p>The <code>twisted.tap</code> framework is one of the reasons
+ servers can be set up with little knowledge and time. Simply
+ running <code>mktap</code> with arguments can bring up a web
+ server, a mail server or an integrated chat server -- with
+ hardly any need for maintainance. As a working
+ proof-on-concept, the <code>tap2deb</code> utility exists to
+ wrap up tap files in Debian packages, which include scripts for
+ running and stopping the server and interact with
+ <code>init(8)</code> to make sure servers are automatically run
+ on start-up. Such programs can also be written to interface
+ with the Red Hat Package Manager or the FreeBSD package
+ management systems.</p>
+
+ <blockquote>
+<pre class="shell">
+% mktap --uid 33 --gid 33 web --static /var/www --port 80
+% tap2deb -t web.tap -m 'Moshe Zadka &lt;moshez@debian.org&gt;'
+% su
+password:
+# dpkg -i .build/twisted-web_1.0_all.deb
+</pre>
+
+ <h6>Listing 8: Bringing up a web server on a Debian
+ system</h6>
+ </blockquote>
+
+ <h3>Multi-thread Support</h3>
+
+ <p>Sometimes, threads are unavoidable or hard to avoid. Many
+ legacy programs which use threads want to use Twisted, and some
+ vendor APIs have no non-blocking version -- for example, most
+ database systems' API. Twisted can work with threads, although
+ it supports only one thread in which the main select loop is
+ running. It can use other threads to simulate non-blocking API
+ over a blocking API -- it spawns a thread to call the blocking
+ API, and when it returns, the thread calls a callback in the
+ main thread. Threads can call callbacks in the main thread
+ safely by adding those callbacks to a list of pending events.
+ When the main thread is between select calls, it searches
+ through the list of pending events, and executes them. This is
+ used in the <code>twisted.enterprise</code> package to supply
+ an event driven interfaces to databases, which uses Python's DB
+ API.</p>
+
+ <p>Twisted tries to optimize for the common case -- no threads.
+ If there is need for threads, a special call must be made to
+ inform the <code>twisted.python.threadable</code> module that
+ threads will be used. This module is implemented differently
+ depending on whether threads will be used or not. The decision
+ must be made before importing any modules which use threadable,
+ and so is usually done in the main application. For example,
+ <code>twistd</code> has a command line option to initialize
+ threads.</p>
+
+ <p>Twisted also supplies a module which supports a threadpool,
+ so the common task of implementing non-blocking APIs above
+ blocking APIs will be both easy and efficient. Threads are kept
+ in a pool, and dispatch requests are done by threads which are
+ not working. The pool supports a maximum amount of threads, and
+ will throw exceptions when there are more requests than
+ allowable threads.</p>
+
+ <p>One of the difficulties about multi-threaded systems is
+ using locks to avoid race conditions. Twisted uses a mechanism
+ similar to Java's synchronized methods. A class can declare a
+ list of methods which cannot safely be called at the same time
+ from two different threads. A function in threadable then uses
+ <code>twisted.python.hook</code> to transparently add
+ lock/unlock around these methods. This allows Twisted classes
+ to be written without thought about threading, except for one
+ localized declaration which does not entail any performance
+ penalty for the single-threaded case.</p>
+
+ <h3>Twisted Mail Server</h3>
+
+ <p>Mail servers have a history of security flaws. Sendmail is
+ by now the poster boy of security holes, but no mail servers,
+ bar maybe qmail, are free of them. Like Dan Bernstein of qmail
+ fame said, mail cannot be simply turned off -- even the
+ simplest organization needs a mail server. Since Twisted is
+ written in a high-level language, many problems which plague
+ other mail servers, notably buffer overflows, simply do not
+ exist. Other holes are avoidable with correct design. Twisted
+ Mail is a project trying to see if it is possible to write a
+ high quality high performance mail server entirely in
+ Python.</p>
+
+ <p>Twisted Mail is built on the SMTP server and client protocol
+ classes. While these present a level of abstraction from the
+ specific SMTP line semantics, they do not contain any message
+ storage code. The SMTP server class does know how to divide
+ responsibility between domains. When a message arrives, it
+ analyzes the recipient's address, tries matching it with one of
+ the registered domain, and then passes validation of the
+ address and saving the message to the correct domain, or
+ refuses to handle the message if it cannot handle the domain.
+ It is possible to specify a catch-all domain, which will
+ usually be responsible for relaying mails outwards.</p>
+
+ <p>While correct relaying is planned for the future, at the
+ moment we have only so-called "smarthost" relaying. All e-mail
+ not recognized by a local domain is relayed to a single outside
+ upstream server, which is supposed to relay the mail further.
+ This is the configuration for most home machines, which are
+ Twisted Mail's current target audience.</p>
+
+ <p>Since the people involved in Twisted's development were
+ reluctant to run code that runs as a super user, or with any
+ special privileges, it had to be considered how delivery of
+ mail to users is possible. The solution decided upon was to
+ have Twisted deliver to its own directory, which should have
+ very strict permissions, and have users pull the mail using
+ some remote mail access protocol like POP3. This means only a
+ user would write to his own mail box, so no security holes in
+ Twisted would be able to adversely affect a user.</p>
+
+ <p>Future plans are to use a Perspective Broker-based service
+ to hand mail to users to a personal server using a UNIX domain
+ socket, as well as to add some more conventional delivery
+ methods, as scary as they may be.</p>
+
+ <p>Because the default configuration of Twisted Mail is to be
+ an integrated POP3/SMTP servers, it is ideally suited for the
+ so-called POP toaster configuration, where there are a
+ multitude of virtual users and domains, all using the same IP
+ address and computer to send and receive mails. It is fairly
+ easy to configure Twisted as a POP toaster. There are a number
+ of deployment choices: one can append a telnet server to the
+ tap for remote configuration, or simple scripts can add and
+ remove users from the user database. The user database is saved
+ as a directory, where file names are keys and file contents are
+ values, so concurrency is not usually a problem.</p>
+
+ <blockquote>
+<pre class="shell">
+% mktap mail -d foobar.com=$HOME/Maildir/ -u postmaster=secret -b \
+ -p 110 -s 25
+% twistd -f mail.tap
+
+</pre>
+
+ <h6>Bringing up a simple mail-server</h6>
+ </blockquote>
+
+ <p>Twisted's native mail storage format is Maildir, a format
+ that requires no locking and is safe and atomic. Twisted
+ supports a number of standardized extensions to Maildir,
+ commonly known as Maildir++. Most importantly, it supports
+ deletion as simply moving to a subfolder named
+ <code>Trash</code>, so mail is recoverable if accessed through
+ a protocol which allows multiple folders, like IMAP. However,
+ Twisted itself currently does not support any such protocol
+ yet.</p>
+
+ <h3>Introducing Perspective Broker</h3>
+
+ <h4>All the World's a Game</h4>
+
+ <p>Twisted was originally designed to support multi-player
+ games; a simulated "real world" environment. Experience with
+ game systems of that type is enlightening as to the nature of
+ computing on the whole. Almost all services on a computer are
+ modeled after some simulated real-world activity. For example,
+ e-"mail", or "document publishing" on the web. Even
+ "object-oriented" programming is based around the notion that
+ data structures in a computer simulate some analogous
+ real-world objects.</p>
+
+ <p>All such networked simulations have a few things in common.
+ They each represent a service provided by software, and there
+ is usually some object where "global" state is kept. Such a
+ service must provide an authentication mechanism. Often, there
+ is a representation of the authenticated user within the
+ context of the simulation, and there are also objects aside
+ from the user and the simulation itself that can be
+ accessed.</p>
+
+ <p>For most existing protocols, Twisted provides these
+ abstractions through <code>twisted.internet.passport</code>.
+ This is so named because the most important common
+ functionality it provides is authentication. A simulation
+ "world" as described above -- such as an e-mail system,
+ document publishing archive, or online video game -- is
+ represented by subclass of <code>Service</code>, the
+ authentication mechanism by an <code>Authorizer</code> (which
+ is a set of <code>Identities</code>), and the user of the
+ simulation by a <code>Perspective</code>. Other objects in the
+ simulation may be represented by arbitrary python objects,
+ depending upon the implementation of the given protocol.</p>
+
+ <p>New problem domains, however, often require new protocols,
+ and re-implementing these abstractions each time can be
+ tedious, especially when it's not necessary. Many efforts have
+ been made in recent years to create generic "remote object" or
+ "remote procedure call" protocols, but in developing Twisted,
+ these protocols were found to require too much overhead in
+ development, be too inefficient at runtime, or both.</p>
+
+ <p>Perspective Broker is a new remote-object protocol designed
+ to be lightweight and impose minimal constraints upon the
+ development process and use Python's dynamic nature to good
+ effect, but still relatively efficient in terms of bandwidth
+ and CPU utilization. <code>twisted.spread.pb</code> serves as a
+ reference implementation of the protocol, but implementation of
+ Perspective Broker in other languages is already underway.
+ <code>spread</code> is the <code>twisted</code> subpackage
+ dealing with remote calls and objects, and has nothing to do
+ with the <code>spread</code> toolkit.</p>
+
+ <p>Perspective Broker extends
+ <code>twisted.internet.passport</code>'s abstractions to be
+ concrete objects rather than design patterns. Rather than
+ having a <code>Protocol</code> implementation translate between
+ sequences of bytes and specifically named methods (as in the
+ other Twisted <code>Protocols</code>), Perspective Broker
+ defines a direct mapping between network messages and
+ quasi-arbitrary method calls.</p>
+
+ <h3>Translucent, not Transparent</h3>
+
+ <p>In a server application where a large number of clients may
+ be interacting at once, it is not feasible to have an
+ arbitrarily large number of OS threads blocking and waiting for
+ remote method calls to return. Additionally, the ability for
+ any client to call any method of an object would present a
+ significant security risk. Therefore, rather than attempting to
+ provide a transparent interface to remote objects,
+ <code>twisted.spread.pb</code> is "translucent", meaning that
+ while remote method calls have different semantics than local
+ ones, the similarities in semantics are mirrored by
+ similarities in the syntax. Remote method calls impose as
+ little overhead as possible in terms of volume of code, but "as
+ little as possible" is unfortunately not "nothing".</p>
+
+ <p><code>twisted.spread.pb</code> defines a method naming
+ standard for each type of remotely accessible object. For
+ example, if a client requests a method call with an expression
+ such as <code>myPerspective.doThisAction()</code>, the remote
+ version of <code>myPerspective</code> would be sent the message
+ <code>perspective_doThisAction</code>. Depending on the manner
+ in which an object is accessed, other method prefixes may be
+ <code>observe_</code>, <code>view_</code>, or
+ <code>remote_</code>. Any method present on a remotely
+ accessible object, and named appropriately, is considered to be
+ published -- since this is accomplished with
+ <code>getattr</code>, the definition of "present" is not just
+ limited to methods defined on the class, but instances may have
+ arbitrary callable objects associated with them as long as the
+ name is correct -- similarly to normal python objects.</p>
+
+ <p>Remote method calls are made on remote reference objects
+ (instances of <code>pb.RemoteReference</code>) by calling a
+ method with an appropriate name. However, that call will not
+ block -- if you need the result from a remote method call, you
+ pass in one of the two special keyword arguments to that method
+ -- <code>pbcallback</code> or <code>pberrback</code>.
+ <code>pbcallback</code> is a callable object which will be
+ called when the result is available, and <code>pberrback</code>
+ is a callable object which will be called if there was an
+ exception thrown either in transmission of the call or on the
+ remote side.</p>
+
+ <p>In the case that neither <code>pberrback</code> or
+ <code>pbcallback</code> is provided,
+ <code>twisted.spread.pb</code> will optimize network usage by
+ not sending confirmations of messages.</p>
+
+ <blockquote>
+<pre class="python">
+# Server Side
+class MyObject(pb.Referenceable):
+ def remote_doIt(self):
+ return "did it"
+
+# Client Side
+ ...
+ def myCallback(result):
+ print result # result will be 'did it'
+ def myErrback(stacktrace):
+ print 'oh no, mr. bill!'
+ print stacktrace
+ myRemoteReference.doIt(pbcallback=myCallback,
+ pberrback=myErrback)
+</pre>
+
+ <h6>Listing 9: A remotely accessible object and accompanying
+ call</h6>
+ </blockquote>
+
+ <h3>Different Behavior for Different Perspectives</h3>
+
+ <p>Considering the problem of remote object access in terms of
+ a simulation demonstrates a requirement for the knowledge of an
+ actor with certain actions or requests. Often, when processing
+ message, it is useful to know who sent it, since different
+ results may be required depending on the permissions or state
+ of the caller.</p>
+
+ <p>A simple example is a game where certain an object is
+ invisible, but players with the "Heightened Perception"
+ enchantment can see it. When answering the question "What
+ objects are here?" it is important for the room to know who is
+ asking, to determine which objects they can see. Parallels to
+ the differences between "administrators" and "users" on an
+ average multi-user system are obvious.</p>
+
+ <p>Perspective Broker is named for the fact that it does not
+ broker only objects, but views of objects. As a user of the
+ <code>twisted.spread.pb</code> module, it is quite easy to
+ determine the caller of a method. All you have to do is
+ subclass <code>Viewable</code>.</p>
+
+ <blockquote>
+<pre class="python">
+# Server Side
+class Greeter(pb.Viewable):
+ def view_greet(self, actor):
+ return "Hello %s!\n" % actor.perspectiveName
+
+# Client Side
+ ...
+ remoteGreeter.greet(pbcallback=sys.stdout.write)
+ ...
+</pre>
+
+ <h6>Listing 10: An object responding to its calling
+ perspective</h6>
+ </blockquote>
+ Before any arguments sent by the client, the actor
+ (specifically, the Perspective instance through which this
+ object was retrieved) will be passed as the first argument to
+ any <code>view_xxx</code> methods.
+
+ <h3>Mechanisms for Sharing State</h3>
+
+ <p>In a simulation of any decent complexity, client and server
+ will wish to share structured data. Perspective Broker provides
+ a mechanism for both transferring (copying) and sharing
+ (caching) that state.</p>
+
+ <p>Whenever an object is passed as an argument to or returned
+ from a remote method call, that object is serialized using
+ <code>twisted.spread.jelly</code>; a serializer similar in some
+ ways to Python's native <code>pickle</code>. Originally,
+ <code>pickle</code> itself was going to be used, but there were
+ several security issues with the <code>pickle</code> code as it
+ stands. It is on these issues of security that
+ <code>pickle</code> and <code>twisted.spread.jelly</code> part
+ ways.</p>
+
+ <p>While <code>twisted.spread.jelly</code> handles a few basic
+ types such as strings, lists, dictionaries and numbers
+ automatically, all user-defined types must be registered both
+ for serialization and unserialization. This registration
+ process is necessary on the sending side in order to determine
+ if a particular object is shared, and whether it is shared as
+ state or behavior. On the receiving end, it's necessary to
+ prevent arbitrary code from being run when an object is
+ unserialized -- a significant security hole in
+ <code>pickle</code> for networked applications.</p>
+
+ <p>On the sending side, the registration is accomplished by
+ making the object you want to serialize a subclass of one of
+ the "flavors" of object that are handled by Perspective Broker.
+ A class may be <code>Referenceable</code>,
+ <code>Viewable</code>, <code>Copyable</code> or
+ <code>Cacheable</code>. These four classes correspond to
+ different ways that the object will be seen remotely.
+ Serialization flavors are mutually exclusive -- these 4 classes
+ may not be mixed in with each other.</p>
+
+ <ul>
+ <li><code>Referenceable</code>: The remote side will refer to
+ this object directly. Methods with the prefix
+ <code>remote_</code> will be callable on it. No state will be
+ transferred.</li>
+
+ <li><code>Viewable</code>: The remote side will refer to a
+ proxy for this object, which indicates what perspective
+ accessed this; as discussed above. Methods with the prefix
+ <code>view_</code> will be callable on it, and have an
+ additional first argument inserted (the perspective that
+ called the method). No state will be transferred.</li>
+
+ <li><code>Copyable</code>: Each time this object is
+ serialized, its state will be copied and sent. No methods are
+ remotely callable on it. By default, the state sent will be
+ the instance's <code>__dict__</code>, but a method
+ <code>getStateToCopyFor(perspective)</code> may be defined
+ which returns an arbitrary serializable object for
+ state.</li>
+
+ <li><code>Cacheable</code>: The first time this object is
+ serialized, its state will be copied and sent. Each
+ subsequent time, however, a reference to the original object
+ will be sent to the receiver. No methods will be remotely
+ callable on this object. By default, again, the state sent
+ will be the instance's <code>__dict__</code>but a method
+ <code>getStateToCacheAndObserveFor(perspective,
+ observer)</code> may be defined to return alternative state.
+ Since the state for this object is only sent once, the
+ <code>observer</code> argument is an object representative of
+ the receiver's representation of the <code>Cacheable</code>
+ after unserialization -- method calls to this object will be
+ resolved to methods prefixed with <code>observe_</code>,
+ <em>on the receiver's <code>RemoteCache</code> of this
+ object</em>. This may be used to keep the receiver's cache
+ up-to-date as relevant portions of the <code>Cacheable</code>
+ object change.</li>
+ </ul>
+
+ <h3>Publishing Objects with PB</h3>
+
+ <p>The previous samples of code have shown how an individual
+ object will interact over a previously-established PB
+ connection. In order to get to that connection, you need to do
+ some set-up work on both the client and server side; PB
+ attempts to minimize this effort.</p>
+
+ <p>There are two different approaches for setting up a PB
+ server, depending on your application's needs. In the simplest
+ case, where your application does not deal with the
+ abstractions above -- services, identities, and perspectives --
+ you can simply publish an object on a particular port.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class Echoer(pb.Root):
+ def remote_echo(self, st):
+ print 'echoing:', st
+ return st
+if __name__ == '__main__':
+ app = main.Application("pbsimple")
+ app.listenOn(8789, pb.BrokerFactory(Echoer()))
+ app.run()
+</pre>
+
+ <h6>Listing 11: Creating a simple PB server</h6>
+ </blockquote>
+
+ <p>Listing 11 shows how to publish a simple object which
+ responds to a single message, "echo", and returns whatever
+ argument is sent to it. There is very little to explain: the
+ "Echoer" class is a pb.Root, which is a small subclass of
+ Referenceable designed to be used for objects published by a
+ BrokerFactory, so Echoer follows the same rule for remote
+ access that Referenceable does. Connecting to this service is
+ almost equally simple.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def gotObject(object):
+ print "got object:",object
+ object.echo("hello network", pbcallback=gotEcho)
+def gotEcho(echo):
+ print 'server echoed:',echo
+ main.shutDown()
+def gotNoObject(reason):
+ print "no object:",reason
+ main.shutDown()
+pb.getObjectAt("localhost", 8789, gotObject, gotNoObject, 30)
+main.run()
+</pre>
+
+ <h6>Listing 12: A client for Echoer objects.</h6>
+ </blockquote>
+
+ <p>The utility function <code>pb.getObjectAt</code> retrieves
+ the root object from a hostname/port-number pair and makes a
+ callback (in this case, <code>gotObject</code>) if it can
+ connect and retrieve the object reference successfully, and an
+ error callback (<code>gotNoObject</code>) if it cannot connect
+ or the connection times out.</p>
+
+ <p><code>gotObject</code> receives the remote reference, and
+ sends the <code>echo</code> message to it. This call is
+ visually noticeable as a remote method invocation by the
+ distinctive <code>pbcallback</code> keyword argument. When the
+ result from that call is received, <code>gotEcho</code> will be
+ called, notifying us that in fact, the server echoed our input
+ ("hello network").</p>
+
+ <p>While this setup might be useful for certain simple types of
+ applications where there is no notion of a "user", the
+ additional complexity necessary for authentication and service
+ segregation is worth it. In particular, re-use of server code
+ for things like chat (twisted.words) is a lot easier with a
+ unified notion of users and authentication.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class SimplePerspective(pb.Perspective):
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+class SimpleService(pb.Service):
+ def getPerspectiveNamed(self, name):
+ return SimplePerspective(name, self)
+if __name__ == '__main__':
+ import pbecho
+ app = main.Application("pbecho")
+ pbecho.SimpleService("pbecho",app).getPerspectiveNamed("guest")\
+ .makeIdentity("guest")
+ app.listenOn(pb.portno, pb.BrokerFactory(pb.AuthRoot(app)))
+ app.save("start")
+</pre>
+
+ <h6>Listing 13: A PB server using twisted's "passport"
+ authentication.</h6>
+ </blockquote>
+
+ <p>In terms of the "functionality" it offers, this server is
+ identical. It provides a method which will echo some simple
+ object sent to it. However, this server provides it in a manner
+ which will allow it to cooperate with multiple other
+ authenticated services running on the same connection, because
+ it uses the central Authorizer for the application.</p>
+
+ <p>On the line that creates the <code>SimpleService</code>,
+ several things happen.</p>
+
+ <ol>
+ <li>A SimpleService is created and persistently added to the
+ <code>Application</code> instance.</li>
+
+ <li>A SimplePerspective is created, via the overridden
+ <code>getPerspectiveNamed</code> method.</li>
+
+ <li>That <code>SimplePerspective</code> has an
+ <code>Identity</code> generated for it, and persistently
+ added to the <code>Application</code>'s
+ <code>Authorizer</code>. The created identity will have the
+ same name as the perspective ("guest"), and the password
+ supplied (also, "guest"). It will also have a reference to
+ the service "pbecho" and a perspective named "guest", by
+ name. The <code>Perspective.makeIdentity</code> utility
+ method prevents having to deal with the intricacies of the
+ passport <code>Authorizer</code> system when one doesn't
+ require strongly separate <code>Identity</code>s and
+ <code>Perspective</code>s.</li>
+ </ol>
+ <br />
+ <br />
+
+
+ <p>Also, this server does not run itself, but instead persists
+ to a file which can be run with twistd, offering all the usual
+ amenities of daemonization, logging, etc. Once the server is
+ run, connecting to it is similar to the previous example.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def success(message):
+ print "Message received:",message
+ main.shutDown()
+def failure(error):
+ print "Failure...",error
+ main.shutDown()
+def connected(perspective):
+ perspective.echo("hello world",
+ pbcallback=success,
+ pberrback=failure)
+ print "connected."
+pb.connect(connected, failure, "localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 30)
+main.run()
+</pre>
+
+ <h6>Listing 14: Connecting to an Authorized Service</h6>
+ </blockquote>
+ <br />
+ <br />
+
+
+ <p>This introduces a new utility -- <code>pb.connect</code>.
+ This function takes a long list of arguments and manages the
+ handshaking and challenge/response aspects of connecting to a
+ PB service perspective, eventually calling back to indicate
+ either success or failure. In this particular example, we are
+ connecting to localhost on the default PB port (8787),
+ authenticating to the identity "guest" with the password
+ "guest", requesting the perspective "guest" from the service
+ "pbecho". If this can't be done within 30 seconds, the
+ connection will abort.</p>
+
+ <p>In these examples, I've attempted to show how Twisted makes
+ event-based scripting easier; this facilitates the ability to
+ run short scripts as part of a long-running process. However,
+ event-based programming is not natural to procedural scripts;
+ it is more generally accepted that GUI programs will be
+ event-driven whereas scripts will be blocking. An alternative
+ client to our <code>SimpleService</code> using GTK illustrates
+ the seamless meshing of Twisted and GTK.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import main, ingtkernet
+from twisted.spread.ui import gtkutil
+import gtk
+ingtkernet.install()
+class EchoClient:
+ def __init__(self, echoer):
+ l.hide()
+ self.echoer = echoer
+ w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.GtkVBox(); b = gtk.GtkButton("Echo:")
+ self.entry = gtk.GtkEntry(); self.outry = gtk.GtkEntry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', gtk.mainquit)
+ w.show_all()
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.echo(txt, pbcallback=self.outry.set_text)
+l = gtkutil.Login(EchoClient, None, initialService="pbecho")
+l.show_all()
+gtk.mainloop()
+</pre>
+
+ <h6>Listing 15: A Twisted GUI application</h6>
+ </blockquote>
+
+ <h3>Event-Driven Web Object Publishing with Web.Widgets</h3>
+
+ <p>Although PB will be interesting to those people who wish to
+ write custom clients for their networked applications, many
+ prefer or require a web-based front end. Twisted's built-in web
+ server has been designed to accommodate this desire, and the
+ presentation framework that one would use to write such an
+ application is <code>twisted.web.widgets</code>. Web.Widgets
+ has been designed to work in an event-based manner, without
+ adding overhead to the designer or the developer's
+ work-flow.</p>
+
+ <p>Surprisingly, asynchronous web interfaces fit very well into
+ the normal uses of purpose-built web toolkits such as PHP. Any
+ experienced PHP, Zope, or WebWare developer will tell you that
+ <em>separation of presentation, content, and logic</em> is very
+ important. In practice, this results in a "header" block of
+ code which sets up various functions which are called
+ throughout the page, some of which load blocks of content to
+ display. While PHP does not enforce this, it is certainly
+ idiomatic. Zope enforces it to a limited degree, although it
+ still allows control structures and other programmatic elements
+ in the body of the content.</p>
+
+ <p>In Web.Widgets, strict enforcement of this principle
+ coincides very neatly with a "hands-free" event-based
+ integration, where much of the work of declaring callbacks is
+ implicit. A "Presentation" has a very simple structure for
+ evaluating Python expressions and giving them a context to
+ operate in. The "header" block which is common to many
+ templating systems becomes a class, which represents an
+ enumeration of events that the template may generate, each of
+ which may be responded to either immediately or latently.</p>
+
+ <p>For the sake of simplicity, as well as maintaining
+ compatibility for potential document formats other than HTML,
+ Presentation widgets do not attempt to parse their template as
+ HTML tags. The structure of the template is <code>"HTML Text
+ %%%%python_expression()%%%% more HTML Text"</code>. Every set
+ of 4 percent signs (%%%%) switches back and forth between
+ evaluation and printing.</p>
+
+ <p>No control structures are allowed in the template. This was
+ originally thought to be a potentially major inconvenience, but
+ with use of the Web.Widgets code to develop a few small sites,
+ it has seemed trivial to encapsulate any table-formatting code
+ within a method; especially since those methods can take string
+ arguments if there's a need to customize the table's
+ appearance.</p>
+
+ <p>The namespace for evaluating the template expressions is
+ obtained by scanning the class hierarchy for attributes, and
+ getting each of those attributes from the current instance.
+ This means that all methods will be bound methods, so
+ indicating "self" explicitly is not required. While it is
+ possible to override the method for creating namespaces, using
+ this default has the effect of associating all presentation
+ code for a particular widget in one class, along with its
+ template. If one is working with a non-programmer designer, and
+ the template is in an external file, it is always very clear to
+ the designer what functionality is available to them in any
+ given scope, because there is a list of available methods for
+ any given class.</p>
+
+ <p>A convenient event to register for would be a response from
+ the PB service that we just implemented. We can use the
+ <code>Deferred</code> class in order to indicate to the widgets
+ framework that certain work has to be done later. This is a
+ Twisted convention which one can currently use in PB as well as
+ webwidgets; any framework which needs the ability to defer a
+ return value until later should use this facility. Elements of
+ the page will be rendered from top to bottom as data becomes
+ available, so the page will not be blocked on rendering until
+ all deferred elements have been completed.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.python import defer
+from twisted.web import widgets
+class EchoDisplay(widgets.Presentation):
+ template = """&lt;H1&gt;Welcome to my widget, displaying %%%%echotext%%%%.&lt;/h1&gt;
+ &lt;p&gt;Here it is: %%%%getEchoPerspective()%%%%&lt;/p&gt;"""
+ echotext = 'hello web!'
+ def getEchoPerspective(self):
+ d = defer.Deferred()
+ pb.connect(d.callback, d.errback, "localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 1)
+ d.addCallbacks(self.makeListOf, self.formatTraceback)
+ return ['&lt;b&gt;',d,'&lt;/b&gt;']
+ def makeListOf(self, echoer):
+ d = defer.Deferred()
+ echoer.echo(self.echotext, pbcallback=d.callback, pberrback=d.errback)
+ d.addCallbacks(widgets.listify, self.formatTraceback)
+ return [d]
+if __name__ == "__main__":
+ from twisted.web import server
+ from twisted.internet import main
+ a = main.Application("pbweb")
+ gdgt = widgets.Gadget()
+ gdgt.widgets['index'] = EchoDisplay()
+ a.listenOn(8080, server.Site(gdgt))
+ a.run()
+</pre>
+
+ <h6>Listing 16: an event-based web widget.</h6>
+ </blockquote>
+
+ <p>Each time a Deferred is returned as part of the page, the
+ page will pause rendering until the deferred's
+ <code>callback</code> method is invoked. When that callback is
+ made, it is inserted at the point in the page where rendering
+ left off.</p>
+
+ <p>If necessary, there are options within web.widgets to allow
+ a widget to postpone or cease rendering of the entire page --
+ for example, it is possible to write a FileDownload widget,
+ which will override the rendering of the entire page and
+ replace it with a file download.</p>
+
+ <p>The final goal of web.widgets is to provide a framework
+ which encourages the development of usable library code. Too
+ much web-based code is thrown away due to its particular
+ environment requirements or stylistic preconceptions it carries
+ with it. The goal is to combine the fast-and-loose iterative
+ development cycle of PHP with the ease of installation and use
+ of Zope's "Product" plugins.</p>
+
+ <h3>Things That Twisted Does Not Do</h3>
+
+ <p>It is unfortunately well beyond the scope of this paper to
+ cover all the functionality that Twisted provides, but it
+ serves as a good overview. It may seem as though twisted does
+ anything and everything, but there are certain features we
+ never plan to implement because they are simply outside the
+ scope of the project.</p>
+
+ <p>Despite the multiple ways to publish and access objects,
+ Twisted does not have or support an interface definition
+ language. Some developers on the Twisted project have
+ experience with remote object interfaces that require explicit
+ specification of all datatypes during the design of an object's
+ interface. We feel that such interfaces are in the spirit of
+ statically-typed languages, and are therefore suited to the
+ domain of problems where statically-typed languages excel.
+ Twisted has no plans to implement a protocol schema or static
+ type-checking mechanism, as the efficiency gained by such an
+ approach would be quickly lost again by requiring the type
+ conversion between Python's dynamic types and the protocol's
+ static ones. Since one of the key advantages of Python is its
+ extremely flexible dynamic type system, we felt that a
+ dynamically typed approach to protocol design would share some
+ of those advantages.</p>
+
+ <p>Twisted does not assume that all data is stored in a
+ relational database, or even an efficient object database.
+ Currently, Twisted's configuration state is all stored in
+ memory at run-time, and the persistent parts of it are pickled
+ at one go. There are no plans to move the configuration objects
+ into a "real" database, as we feel it is easier to keep a naive
+ form of persistence for the default case and let
+ application-specific persistence mechanisms handle persistence.
+ Consequently, there is no object-relational mapping in Twisted;
+ <code>twisted.enterprise</code> is an interface to the
+ relational paradigm, not an object-oriented layer over it.</p>
+
+ <p>There are other things that Twisted will not do as well, but
+ these have been frequently discussed as possibilities for it.
+ The general rule of thumb is that if something will increase
+ the required installation overhead, then Twisted will probably
+ not do it. Optional additions that enhance integration with
+ external systems are always welcome: for example, database
+ drivers for Twisted or a CORBA IDL for PB objects.</p>
+
+ <h3>Future Directions</h3>
+
+ <p>Twisted is still a work in progress. The number of protocols
+ in the world is infinite for all practical purposes, and it
+ would be nice to have a central repository of event-based
+ protocol implementations. Better integration with frameworks
+ and operating systems is also a goal. Examples for integration
+ opportunities are automatic creation of installer for "tap"
+ files (for Red Hat Packager-based distributions, FreeBSD's
+ package management system or Microsoft Windows(tm) installers),
+ and integration with other event-dispatch mechanisms, such as
+ win32's native message dispatch.</p>
+
+ <p>A still-nascent feature of Twisted, which this paper only
+ touches briefly upon, is <code>twisted.enterprise</code>: it is
+ planned that Twisted will have first-class database support
+ some time in the near future. In particular, integration
+ between twisted.web and twisted.enterprise to allow developers
+ to have SQL conveniences that they are used to from other
+ frameworks.</p>
+
+ <p>Another direction that we hope Twisted will progress in is
+ standardization and porting of PB as a messaging protocol. Some
+ progress has already been made in that direction, with XEmacs
+ integration nearly ready for release as of this writing.</p>
+
+ <p>Tighter integration of protocols is also a future goal, such
+ an FTP server that can serve the same resources as a web
+ server, or a web server that allows users to change their POP3
+ password. While Twisted is already a very tightly integrated
+ framework, there is always room for more integration. Of
+ course, all this should be done in a flexible way, so the
+ end-user will choose which components to use -- and have those
+ components work well together.</p>
+
+ <h3>Conclusions</h3>
+
+ <p>As shown, Twisted provides a lot of functionality to the
+ Python network programmer, while trying to be in his way as
+ little as possible. Twisted gives good tools for both someone
+ trying to implement a new protocol, or someone trying to use an
+ existing protocol. Twisted allows developers to prototype and
+ develop object communication models with PB, without designing
+ a byte-level protocol. Twisted tries to have an easy way to
+ record useful deployment options, via the
+ <code>twisted.tap</code> and plugin mechanisms, while making it
+ easy to generate new forms of deployment. And last but not
+ least, even Twisted is written in a high-level language and
+ uses its dynamic facilities to give an easy API, it has
+ performance which is good enough for most situations -- for
+ example, the web server can easily saturate a T1 line serving
+ dynamic requests on low-end machines.</p>
+
+ <p>While still an active project, Twisted can already used for
+ production programs. Twisted can be downloaded from the main
+ Twisted site (http://www.twistedmatrix.com) where there is also
+ documentation for using and programming Twisted.</p>
+
+ <h3>Acknowledgements</h3>
+
+ <p>We wish to thank Sean Riley, Allen Short, Chris Armstrong,
+ Paul Swartz, J&uuml;rgen Hermann, Benjamin Bruheim, Travis B.
+ Hartwell, and Itamar Shtull-Trauring for being a part of the
+ Twisted development team with us.</p>
+
+ <p>Thanks also to Jason Asbahr, Tommi Virtanen, Gavin Cooper,
+ Erno Kuusela, Nick Moffit, Jeremy Fincher, Jerry Hebert, Keith
+ Zaback, Matthew Walker, and Dan Moniz, for providing insight,
+ commentary, bandwidth, crazy ideas, and bug-fixes (in no
+ particular order) to the Twisted team.</p>
+
+ <h3>References</h3>
+
+ <ol>
+ <li>The Twisted site, http://www.twistedmatrix.com</li>
+
+ <li>Douglas Schmidt, Michael Stal, Hans Rohnert and Frank
+ Buschmann, Pattern-Oriented Software Architecture, Volume 2,
+ Patterns for Concurrent and Networked Objects, John Wiley
+ &amp; Sons</li>
+
+ <li>Abhishek Chandra, David Mosberger, Scalability of Linux
+ Event-Dispatch Mechanisms, USENIX 2001,
+ http://lass.cs.umass.edu/~abhishek/papers/usenix01/paper.ps</li>
+
+ <li>Protocol specifications, http://www.rfc-editor.com</li>
+
+ <li>The Twisted Philosophical FAQ,
+ http://www.twistedmatrix.com/page.epy/twistedphil.html</li>
+
+ <li>Twisted Advocacy,
+ http://www.twistedmatrix.com/page.epy/whytwisted.html</li>
+
+ <li>Medusa, http://www.nightmare.com/medusa/index.html</li>
+
+ <li>Using Spreadable Web Servers,
+ http://www.twistedmatrix.com/users/jh.twistd/python/moin.cgi/TwistedWeb</li>
+
+ <li>Twisted Spread implementations for other languages,
+ http://www.twistedmatrix.com/users/washort/</li>
+
+ <li>PHP: Hypertext Preprocessor, http://www.php.net/</li>
+
+ <li>The Z Object Publishing Environment,
+ http://www.zope.org/, http://zope.com/</li>
+ </ol>
+ </body>
+</html>
+
diff --git a/doc/historic/stylesheet.css b/doc/historic/stylesheet.css
new file mode 100644
index 0000000..8235f6c
--- /dev/null
+++ b/doc/historic/stylesheet.css
@@ -0,0 +1,178 @@
+
+body
+{
+ margin-left: 2em;
+ margin-right: 2em;
+ border: 0px;
+ padding: 0px;
+ font-family: sans-serif;
+ }
+
+.done { color: #005500; background-color: #99ff99 }
+.notdone { color: #550000; background-color: #ff9999;}
+
+pre
+{
+ padding: 1em;
+ border: thin black solid;
+}
+
+.boxed
+{
+ padding: 1em;
+ border: thin black solid;
+}
+
+.shell
+{
+ background-color: #ffffdd;
+}
+
+.python
+{
+ background-color: #dddddd;
+}
+
+.htmlsource
+{
+ background-color: #dddddd;
+}
+
+.py-prototype
+{
+ background-color: #ddddff;
+}
+
+
+.python-interpreter
+{
+ background-color: #ddddff;
+}
+
+.doit
+{
+ border: thin blue dashed ;
+ background-color: #0ef
+}
+
+.py-src-comment
+{
+ color: #1111CC
+}
+
+.py-src-keyword
+{
+ color: #3333CC;
+ font-weight: bold;
+}
+
+.py-src-parameter
+{
+ color: #000066;
+ font-weight: bold;
+}
+
+.py-src-identifier
+{
+ color: #CC0000
+}
+
+.py-src-string
+{
+
+ color: #115511
+}
+
+.py-src-endmarker
+{
+ display: block; /* IE hack; prevents following line from being sucked into the py-listing box. */
+}
+
+.py-listing
+{
+ margin: 1ex;
+ border: thin solid black;
+ background-color: #eee;
+}
+
+.py-listing pre
+{
+ margin: 0px;
+ border: none;
+ border-bottom: thin solid black;
+}
+
+.py-listing .python
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.py-listing .htmlsource
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.py-caption
+{
+ text-align: center;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+}
+
+.py-filename
+{
+ font-style: italic;
+ }
+
+.manhole-output
+{
+ color: blue;
+}
+
+hr
+{
+ display: inline;
+ }
+
+ul
+{
+ padding: 0px;
+ margin: 0px;
+ margin-left: 1em;
+ padding-left: 1em;
+ border-left: 1em;
+ }
+
+li
+{
+ padding: 2px;
+ }
+
+dt
+{
+ font-weight: bold;
+ margin-left: 1ex;
+ }
+
+dd
+{
+ margin-bottom: 1em;
+ }
+
+div.note
+{
+ background-color: #FFFFCC;
+ margin-top: 1ex;
+ margin-left: 5%;
+ margin-right: 5%;
+ padding-top: 1ex;
+ padding-left: 5%;
+ padding-right: 5%;
+ border: thin black solid;
+}
diff --git a/doc/historic/template-notoc.tpl b/doc/historic/template-notoc.tpl
new file mode 100644
index 0000000..5cc2efc
--- /dev/null
+++ b/doc/historic/template-notoc.tpl
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+ <title></title>
+ <link type="text/css" rel="stylesheet" href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ <div class="body" />
+ </body>
+</html>
diff --git a/doc/historic/template.tpl b/doc/historic/template.tpl
new file mode 100644
index 0000000..bb40833
--- /dev/null
+++ b/doc/historic/template.tpl
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+<title></title>
+<link type="text/css" rel="stylesheet"
+href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title"></h1>
+ <div class="toc"></div>
+ <div class="body">
+
+ </div>
+ </body>
+</html>
+
diff --git a/doc/historic/twisted-debian.html b/doc/historic/twisted-debian.html
new file mode 100644
index 0000000..a88abe5
--- /dev/null
+++ b/doc/historic/twisted-debian.html
@@ -0,0 +1,96 @@
+<html><head><title>Twisted and Debian</title></head><body>
+
+<h1>Twisted and Debian</h1>
+
+<h3>Moshe Zadka</h3>
+<h4>&lt;moshez@debian.org&gt; &lt;moshez@twistedmatrix.com&gt;</h4>
+
+<h2>Twisted</h2>
+
+<p>
+Twisted is a Python networking framework. It is useful for development
+of both clients and servers, and strives to support as many externalities
+as possible -- from network protocols (with over a two dozen RFCs implemented)
+to GUI toolkits (supporting GTK+, Qt, wxWindows and Tk).
+</p>
+
+<h2>Debian</h2>
+
+<p>
+Debian is a free, stable and comprehensive operating system, based on GNU
+software and the Linux kernel. Debian supports eleven hardware archtecture
+and over 6000 programs. Debian, as a free, community-supported, operating
+system, has been used as a base for many other operating systems, including
+Lindows and Knoppix.
+</p>
+
+
+<h2>Using Twisted on a Debian System</h2>
+
+<p>
+The latest stable release of Debian, woody, comes with Twisted 0.15.5 built
+in. New versions of Twisted, which are tested on both stable and unstable,
+are always available from
+"deb http://twistedmatrix.com/users/moshez/apt". So, even those using
+stable Debian can use the latest Twisted releases, including the upcoming 1.0,
+without the overhead of adding unstable sources to their sources.list, dealing
+with apt-pinning or building the sources themselves.
+</p>
+
+<p>
+Of course, users of Debian unstable can get the releases directly from Debian
+-- the released packages, already having been tested on the main Twisted
+Debian machine, are usually uploaded to Debian unstable within hours of
+the official release.
+</p>
+
+<p>
+Twisted supports,
+as fully as possible, the Python versions available in Debian -- currently,
+2.1, 2.2 and pre-releases of 2.3. For those needing just a version of
+Twisted which works with the Debian default Python version, "python-twisted"
+is available. For low-impact on production servers, the documentation of
+Twisted (over half a megabyte) is packaged seperately. Twisted uses
+the Recommends: and Suggests: fields, to allow the Debian packaging tools
+to supply the information about which packages can be used to maximise
+the potential of Twisted.
+</p>
+
+<p>
+For those on the bleeding edge, or people who want to make sure their
+applications work flawlessly for the next version of Twisted, all release
+candidates are available from the apt source
+"deb http://twistedmatrix.com/users/moshez/snapshot". These are the release
+candidates the Twisted team uses itself to prepare for the next release --
+but third party developers interested in assuring compatibility are also
+welcome to use them.
+</p>
+
+<h2>Using Twisted's Debian Integration</h2>
+
+<p>
+For Twisted-based server application developers who want to deploy on
+Debian, Twisted supplies the <code>tap2deb</code> program. This program
+wraps a tap file (Twisted Application Pickle, a Twisted configuration)
+in a Debian archive, including correct installation and removal scripts
+and <code>init.d</code> scripts. For the more savvy Debian users, the
+<code>tap2deb</code> also generates the source package, allowing her
+to modify and polish things which automated software cannot detect
+(such as dependencies or relationships to virtual packages). In addition,
+the Twisted team itself intends to produce Debian packages for some common
+services, such as web servers and an inetd replacement. Those packages
+will enjoy the best of all worlds -- both the consistency which comes
+from being based on the <code>tap2deb</code> and the delicate manual
+tweaking of a Debian maintainer, insuring perfect integration with
+Debian.
+</p>
+
+<p>
+This things will insure you can run a fully functional Debian system
+which relies on Twisted for many of its core, and security sensitive,
+portions -- thus, eliminating many of the classical security holes
+(such as buffer overlows, uninitialized memory access and stack smashing),
+allowing you to sleep better at night.
+</p>
+
+</body></html>
diff --git a/doc/index.xhtml b/doc/index.xhtml
new file mode 100644
index 0000000..d189fd8
--- /dev/null
+++ b/doc/index.xhtml
@@ -0,0 +1,115 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Twisted Documentation Index</title></head><body>
+<h1>Twisted Documentation Index</h1>
+
+<ul>
+ <li>
+ <a href="core/index.xhtml">Twisted Core</a>
+ <ul>
+ <li>
+ <a href="core/howto/index.xhtml">Developer guides</a>
+ </li>
+ <li>
+ <a href="core/examples/index.xhtml">Twisted code examples</a>
+ </li>
+ <li>
+ <a href="core/specifications/index.xhtml">Specifications</a>
+ </li>
+ <li>
+ <a href="core/development/index.xhtml">Development of Twisted</a>
+ </li>
+ <li>
+ <a href="http://twistedmatrix.com/documents/current/api/">API reference</a>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <a href="conch/index.xhtml">Twisted Conch</a>
+ <ul>
+ <li>
+ <a href="conch/howto/index.xhtml">Twisted Conch documentation</a>
+ </li>
+ <li>
+ <a href="conch/examples/index.xhtml">Twisted Conch code examples</a>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <a href="lore/index.xhtml">Twisted Lore</a>
+ <ul>
+ <li>
+ <a href="lore/howto/index.xhtml">Twisted Lore documentation</a>
+ </li>
+ <li>
+ <a href="lore/examples/index.xhtml">Twisted Lore examples</a>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <a href="mail/index.xhtml">Twisted Mail</a>
+ <ul>
+ <li>
+ <a href="mail/tutorial/smtpclient/smtpclient.xhtml">Twisted Mail tutorial</a>
+ </li>
+ <li>
+ <a href="mail/examples/index.xhtml">Twisted Mail code examples</a>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <a href="names/index.xhtml">Twisted Names</a>
+ <ul>
+ <li>
+ <a href="names/howto/index.xhtml">Twisted Names documentation</a>
+ </li>
+ <li>
+ <a href="names/examples/index.xhtml">Twisted Names code examples</a>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <a href="pair/index.xhtml">Twisted Pair</a>
+ <ul>
+ <li>
+ <a href="pair/howto/index.xhtml">Twisted Pair documentation</a>
+ </li>
+ <li>
+ <a href="pair/examples/index.xhtml">Twisted Pair code examples</a>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <a href="web/index.xhtml">Twisted Web</a>
+ <ul>
+ <li>
+ <a href="web/howto/index.xhtml">Twisted Web documentation</a>
+ </li>
+ <li>
+ <a href="web/examples/index.xhtml">Twisted Web code examples</a>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <a href="words/index.xhtml">Twisted Words</a>
+ <ul>
+ <li>
+ <a href="words/howto/index.xhtml">Twisted Words documentation</a>
+ </li>
+ <li>
+ <a href="words/examples/index.xhtml">Twisted Words code examples</a>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <a href="historic/index.xhtml">Historical Documents</a>
+ <ul>
+ <li>
+ <a href="historic/index.xhtml#id1">2003</a>
+ </li>
+ <li>
+ <a href="historic/index.xhtml#previously">Previously</a>
+ </li>
+ </ul>
+ </li>
+</ul>
+
+</body></html>
diff --git a/doc/lore/examples/example.html b/doc/lore/examples/example.html
new file mode 100644
index 0000000..b1c6684
--- /dev/null
+++ b/doc/lore/examples/example.html
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<title>Your Title Here</title>
+</head>
+
+<body>
+<h1>Your Title Here</h1>
+
+<h2>Introduction</h2>
+
+<p>The introduction is an important part of your
+document<span class="footnote">though it should not be the most important
+part</span>.</p>
+
+<div class="note">
+<p>It is generally a <em>very</em> good to write other section except
+the introduction too.</p>
+</div>
+
+<p>
+You can use the following ways to write lists:
+<ul>
+<li>Unordered lists</li>
+<li>
+ <ol>
+ <li>ordered lists</li>
+ </ol>
+</li>
+<li><dl>
+ <dt>defintion lists</dt>
+ <dd>with definitions</dd>
+ </dl>
+</li>
+</ul>
+</p>
+
+<p>Shell commands look like <code class="shell">ls -l</code>. Python
+snippets look like <code class="python">print 1</code>.</p>
+
+<p>Longer python things should be in pre</p>
+
+<pre class="python">
+def foo():
+ pass
+</pre>
+
+<p>Or they can be outlined</p>
+
+<a href="rootscript.py" class="py-listing">rootscript.py</a>
+
+<p>Likewise, HTML can be outlined too:</p>
+
+<a href="example.html" class="py-listing">example.html</a>
+
+</body> </html>
diff --git a/doc/lore/examples/index.html b/doc/lore/examples/index.html
new file mode 100644
index 0000000..b3402b8
--- /dev/null
+++ b/doc/lore/examples/index.html
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Lore examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Lore examples</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+ <ul>
+ <li><a href="example.html" shape="rect">example.html</a></li>
+ <li><a href="slides-template.tpl" shape="rect">slides-template.tpl</a></li>
+ </ul>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/lore/examples/slides-template.tpl b/doc/lore/examples/slides-template.tpl
new file mode 100644
index 0000000..9d7a420
--- /dev/null
+++ b/doc/lore/examples/slides-template.tpl
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+ <title>Twisted Documentation: </title>
+ <link type="text/css" rel="stylesheet" href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ <div><span>Previous: <a class="previous"><span class="previous" /></a></span>
+ <br />
+ <span>Next: <a class="next"><span class="next" /></a></span></div>
+ <h1 class="title"></h1>
+ <div class="body">
+
+ </div>
+ </body>
+</html>
+
diff --git a/doc/lore/howto/extend-lore.html b/doc/lore/howto/extend-lore.html
new file mode 100644
index 0000000..e57a2ff
--- /dev/null
+++ b/doc/lore/howto/extend-lore.html
@@ -0,0 +1,427 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Extending the Lore Documentation System</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Extending the Lore Documentation System</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Inputs and Outputs</a></li><ul><li><a href="#auto2">Creating New Inputs</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Overview<a name="auto0"/></h2>
+
+<p>The <a href="lore.html" shape="rect">Lore Documentation System</a>, out of the box, is
+specialized for documenting Twisted. Its markup includes CSS classes for
+Python, HTML, filenames, and other Twisted-focused categories. But don't
+think this means Lore can't be used for other documentation tasks! Lore is
+designed to allow extensions, giving any Python programmer the ability to
+customize Lore for documenting almost anything.</p>
+
+<p>There are several reasons why you would want to extend Lore. You may want
+to attach file formats Lore does not understand to your documentation. You
+may want to create callouts that have special meanings to the reader, to give a
+memorable appearance to text such as, <q>WARNING: This software was written by
+ a frothing madman!</q> You may want to create color-coding for a different
+programming language, or you may find that Lore does not provide you with
+enough structure to mark your document up completely. All of these situations
+can be solved by creating an extension.</p>
+
+<h2>Inputs and Outputs<a name="auto1"/></h2>
+
+<p>Lore works by reading the HTML source of your document, and
+producing whatever output the user specifies on the command line. If
+the HTML document is well-formed XML that meets a certain minimum
+standard, Lore will be able to to produce some output. All Lore
+extensions will be written to redefine the <em>input</em>, and most
+will redefine the output in some way. The name of the default input
+is <q>lore</q>. When you write your extension, you will come up with
+a new name for your input, telling Lore what rules to use to process
+the file.</p>
+
+<p>Lore can produce XHTML, LaTeX, and DocBook document formats, which can be
+displayed directly if you have a user agent capable of viewing them, or
+processed into a third form such as PostScript or PDF. Another output is
+called <q>lint</q>, after the static-checking utility for C, and is used for
+the same reason: to statically check input files for problems. The
+<q>lint</q> output is just a stream of error messages, not a formatted
+document, but is important because it gives users the ability to validate
+their input before trying to process it. For the first example, the only
+output we will be concerned with is LaTeX.</p>
+
+<h3>Creating New Inputs<a name="auto2"/></h3>
+<p>Create a new input to tell Lore that your document is marked up differently
+from a vanilla Lore document. This gives you the power to define a new tag
+class, for example:</p>
+<pre xml:space="preserve">
+&lt;p&gt;The Frabjulon &lt;span class=&quot;productname&quot;&gt;Limpet 2000&lt;/span&gt;
+is the &lt;span class=&quot;marketinglie&quot;&gt;industry-leading&lt;/span&gt; aquatic
+mollusc counter, bar none.&lt;/p&gt;
+</pre>
+
+<p>The above HTML is an instance of a new input to Lore, which we will call
+MyHTML, to differentiate it from the <q>lore</q> input. We want it to have
+the following markup:</p>
+<ul>
+ <li>A <code>productname</code> class for the &lt;span&gt; tag, which
+ produces underlined text</li>
+ <li>A <code>marketinglie</code> class for &lt;span&gt; tag, which
+ produces larger type, bold text</li>
+</ul>
+<p>Note that I chose class names that are valid Python identifiers. You will
+see why shortly. To get these two effects in Lore's HTML output, all we have
+to do is create a cascading stylesheet (CSS), and use it in the Lore XHTML
+Template. However, we also want these effects to work in LaTeX, and we want
+the output of lint to produce no warnings when it sees lines with these 2
+classes. To make LaTeX and lint work, we start by creating a plugin.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span>.<span class="py-src-variable">scripts</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IProcessor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyHTML</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPlugin</span>, <span class="py-src-variable">IProcessor</span>)
+
+ <span class="py-src-variable">name</span> = <span class="py-src-string">&quot;myhtml&quot;</span>
+ <span class="py-src-variable">moduleName</span> = <span class="py-src-string">&quot;myhtml.factory&quot;</span>
+</pre><div class="caption">
+ Listing 1: The Plugin File - <a href="listings/lore/a_lore_plugin.py"><span class="filename">listings/lore/a_lore_plugin.py</span></a></div></div>
+
+ <p>Create this file in a <code class="shell">twisted/plugins/</code>
+ directory (<em>not</em> a package) which is located in a directory in the
+ Python module search path. See the <a href="../../core/howto/plugin.html" shape="rect">Twisted
+ plugin howto</a> for more details on plugins.</p>
+
+ <p>Users of your extension will pass the value of your plugin's <code class="python">name</code> attribute to lore with the <code class="shell">--input</code> parameter on the command line to select it. For
+ example, to select the plugin defined above, a user would pass <code class="shell">--input myhtml</code>. The <code class="python">moduleName</code> attribute tells Lore where to find the code
+ implementing the plugin. In particular, this module should have a <code class="python">factory</code> attribute which defines a <code class="python">generator_</code>-prefixed method for each output format it
+ supports. Next we'll look at this module.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">default</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myhtml</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">spitters</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyProcessingFunctionFactory</span>(<span class="py-src-parameter">default</span>.<span class="py-src-parameter">ProcessingFunctionFactory</span>):
+ <span class="py-src-variable">latexSpitters</span>={<span class="py-src-variable">None</span>: <span class="py-src-variable">spitters</span>.<span class="py-src-variable">MyLatexSpitter</span>,
+ }
+
+<span class="py-src-comment"># initialize the global variable factory with an instance of your new factory</span>
+<span class="py-src-variable">factory</span>=<span class="py-src-variable">MyProcessingFunctionFactory</span>()
+</pre><div class="caption">Listing 2: The Input
+ Factory - <a href="listings/lore/factory.py-1"><span class="filename">listings/lore/factory.py-1</span></a></div></div>
+
+<p>In Listing 2, we create a subclass of ProcessingFunctionFactory.
+This class provides a hook for you, a class variable
+named <code>latexSpitters</code>. This variable tells Lore what new
+class will be generating LaTeX from your input format. We
+redefine <code>latexSpitters</code> to <code>MyLatexSpitter</code> in
+the subclass because this class knows what to do with the new input we
+have already defined. Last, you must define the module-level
+variable <code class="py-src-identifier">factory</code>. It should be
+an instance with the same interface
+as <code class="py-src-identifier">ProcessingFunctionFactory</code>
+(e.g. an instance of a subclass, in this
+case, <code class="py-src-identifier">MyProcessingFunctionFactory</code>).</p>
+
+<p>Now let's actually write some code to generate the LaTeX. Doing this
+requires at least a familiarity with the LaTeX language. Search Google for
+<q>latex tutorial</q> and you will find any number of useful LaTeX
+resources.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">latex</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span>.<span class="py-src-variable">latex</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">processFile</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyLatexSpitter</span>(<span class="py-src-parameter">latex</span>.<span class="py-src-parameter">LatexSpitter</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">visitNode_span_productname</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">node</span>):
+ <span class="py-src-comment"># start an underline section in LaTeX</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\underline{'</span>)
+ <span class="py-src-comment"># process the node and its children</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">visitNodeDefault</span>(<span class="py-src-variable">node</span>)
+ <span class="py-src-comment"># end the underline block</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'}'</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">visitNode_span_marketinglie</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">node</span>):
+ <span class="py-src-comment"># this example turns on more than one LaTeX effect at once</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\begin{bf}\\begin{Large}'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">visitNodeDefault</span>(<span class="py-src-variable">node</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\end{Large}\\end{bf}'</span>)
+</pre><div class="caption">Listing 3:
+ spitters.py - <a href="listings/lore/spitters.py-1"><span class="filename">listings/lore/spitters.py-1</span></a></div></div>
+
+<p>The method <code>visitNode_span_productname</code> is our handler
+for &lt;span&gt; tags with the <code>class=&quot;productname&quot;</code>
+identifier. Lore knows to try methods <code>visitNode_span_*</code>
+and <code>visitNode_div_*</code> whenever it encounters a new class in
+one of these tags. This is why the class names have to be valid
+Python identifiers.</p>
+
+<p>Now let's see what Lore does with these new classes with the following
+input file:</p>
+
+<div class="html-listing"><pre class="htmlsource">
+&lt;html&gt;
+ &lt;head&gt;
+ &lt;title&gt;My First Example&lt;/title&gt;
+ &lt;/head&gt;
+ &lt;body&gt;
+ &lt;h1&gt;My First Example&lt;/h1&gt;
+ &lt;p&gt;The Frabjulon &lt;span class=&quot;productname&quot;&gt;Limpet 2000&lt;/span&gt;
+ is the &lt;span class=&quot;marketinglie&quot;&gt;industry-leading&lt;/span&gt; aquatic
+ mollusc counter, bar none.&lt;/p&gt;
+ &lt;/body&gt;
+&lt;/html&gt;
+
+</pre><div class="caption">Listing 4:
+ 1st_example.html - <a href="listings/lore/1st_example.html"><span class="filename">listings/lore/1st_example.html</span></a></div></div>
+
+<p>First, verify that your package is laid out correctly. Your directory
+structure should look like this:</p>
+
+<pre xml:space="preserve">
+1st_example.html
+myhtml/
+ __init__.py
+ factory.py
+ spitters.py
+twisted/plugins/
+ a_lore_plugin.py
+</pre>
+
+<p>In the parent directory of myhtml (that is, <code>myhtml/..</code>), run
+lore and pdflatex on the input:</p>
+
+<pre class="shell" xml:space="preserve">
+$ lore --input myhtml --output latex 1st_example.html
+[########################################] (*Done*)
+
+$ pdflatex 1st_example.tex
+[ . . . latex output omitted for brevity . . . ]
+Output written on 1st_example.pdf (1 page, 22260 bytes).
+Transcript written on 1st_example.log.
+</pre>
+
+<p>And here's what the rendered PDF looks like:</p>
+
+<p><img src="../img/myhtml-output.png"/></p>
+
+<p>What happens when we run lore on this file using the lint output?</p>
+
+<pre class="shell" xml:space="preserve">
+$ lore --input myhtml --output lint 1st_example.html
+1st_example.html:7:47: unknown class productname
+1st_example.html:8:38: unknown class marketinglie
+[########################################] (*Done*)
+</pre>
+
+<p>Lint reports these classes as errors, even though our spitter knows how to
+process them. To fix this problem, we must add to <code class="py-filename">factory.py</code>.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">default</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myhtml</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">spitters</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyProcessingFunctionFactory</span>(<span class="py-src-parameter">default</span>.<span class="py-src-parameter">ProcessingFunctionFactory</span>):
+ <span class="py-src-variable">latexSpitters</span>={<span class="py-src-variable">None</span>: <span class="py-src-variable">spitters</span>.<span class="py-src-variable">MyLatexSpitter</span>,
+ }
+
+ <span class="py-src-comment"># redefine getLintChecker to validate our classes</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getLintChecker</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-comment"># use the default checker from parent</span>
+ <span class="py-src-variable">checker</span> = <span class="py-src-variable">lint</span>.<span class="py-src-variable">getDefaultChecker</span>()
+ <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span> = <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>.<span class="py-src-variable">copy</span>()
+ <span class="py-src-variable">oldSpan</span> = <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>[<span class="py-src-string">'span'</span>]
+ <span class="py-src-variable">checkfunc</span>=<span class="py-src-keyword">lambda</span> <span class="py-src-variable">cl</span>: <span class="py-src-variable">oldSpan</span>(<span class="py-src-variable">cl</span>) <span class="py-src-keyword">or</span> <span class="py-src-variable">cl</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">'marketinglie'</span>,
+ <span class="py-src-string">'productname'</span>]
+ <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>[<span class="py-src-string">'span'</span>] = <span class="py-src-variable">checkfunc</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">checker</span>
+
+<span class="py-src-comment"># initialize the global variable factory with an instance of your new factory</span>
+<span class="py-src-variable">factory</span>=<span class="py-src-variable">MyProcessingFunctionFactory</span>()
+</pre><div class="caption">Listing 5: Input
+ Factory with Lint Support - <a href="listings/lore/factory.py-2"><span class="filename">listings/lore/factory.py-2</span></a></div></div>
+
+<p>The method <code class="py-src-identifier">getLintChecker</code> is called
+by Lore to produce the lint output. This modification adds our classes to the
+list of classes lint ignores:</p>
+
+<pre class="shell" xml:space="preserve">
+$ lore --input myhtml --output lint 1st_example.html
+[########################################] (*Done*)
+$ # Hooray!
+</pre>
+
+<p>Finally, there are two other sub-outputs of LaTeX, for a total of three
+different ways that Lore can produce LaTeX: the default way, which produces as
+output an entire, self-contained LaTeX document; with <code class="shell">--config section</code> on the command line, which produces a
+LaTeX \section; and with <code class="shell">--config chapter</code>, which
+produces a LaTeX \chapter. To support these options as well, the solution is
+to make the new spitter class a mixin, and use it with the <code class="py-src-identifier">SectionLatexSpitter</code> and <code class="py-src-identifier">ChapterLatexSpitter</code>, respectively.
+Comments in the following listings tell you everything you need to know about
+making these simple changes:</p>
+
+<ul>
+ <li><div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">default</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myhtml</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">spitters</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyProcessingFunctionFactory</span>(<span class="py-src-parameter">default</span>.<span class="py-src-parameter">ProcessingFunctionFactory</span>):
+ <span class="py-src-comment"># 1. add the keys &quot;chapter&quot; and &quot;section&quot; to latexSpitters to handle the</span>
+ <span class="py-src-comment"># --config chapter and --config section options</span>
+ <span class="py-src-variable">latexSpitters</span>={<span class="py-src-variable">None</span>: <span class="py-src-variable">spitters</span>.<span class="py-src-variable">MyLatexSpitter</span>,
+ <span class="py-src-string">&quot;section&quot;</span>: <span class="py-src-variable">spitters</span>.<span class="py-src-variable">MySectionLatexSpitter</span>,
+ <span class="py-src-string">&quot;chapter&quot;</span>: <span class="py-src-variable">spitters</span>.<span class="py-src-variable">MyChapterLatexSpitter</span>,
+ }
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getLintChecker</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">checker</span> = <span class="py-src-variable">lint</span>.<span class="py-src-variable">getDefaultChecker</span>()
+ <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span> = <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>.<span class="py-src-variable">copy</span>()
+ <span class="py-src-variable">oldSpan</span> = <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>[<span class="py-src-string">'span'</span>]
+ <span class="py-src-variable">checkfunc</span>=<span class="py-src-keyword">lambda</span> <span class="py-src-variable">cl</span>: <span class="py-src-variable">oldSpan</span>(<span class="py-src-variable">cl</span>) <span class="py-src-keyword">or</span> <span class="py-src-variable">cl</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">'marketinglie'</span>,
+ <span class="py-src-string">'productname'</span>]
+ <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>[<span class="py-src-string">'span'</span>] = <span class="py-src-variable">checkfunc</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">checker</span>
+
+<span class="py-src-variable">factory</span>=<span class="py-src-variable">MyProcessingFunctionFactory</span>()
+</pre><div class="caption">factory.py - <a href="listings/lore/factory.py-3"><span class="filename">listings/lore/factory.py-3</span></a></div></div></li>
+ <li><div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">latex</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span>.<span class="py-src-variable">latex</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">processFile</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>
+
+<span class="py-src-comment"># 2. Create a new mixin that does what the old MyLatexSpitter used to do:</span>
+<span class="py-src-comment"># process the new classes we defined</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MySpitterMixin</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">visitNode_span_productname</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">node</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\underline{'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">visitNodeDefault</span>(<span class="py-src-variable">node</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'}'</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">visitNode_span_marketinglie</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">node</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\begin{bf}\\begin{Large}'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">visitNodeDefault</span>(<span class="py-src-variable">node</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\end{Large}\\end{bf}'</span>)
+
+<span class="py-src-comment"># 3. inherit from the mixin class for each of the three sub-spitters</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyLatexSpitter</span>(<span class="py-src-parameter">MySpitterMixin</span>, <span class="py-src-parameter">latex</span>.<span class="py-src-parameter">LatexSpitter</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MySectionLatexSpitter</span>(<span class="py-src-parameter">MySpitterMixin</span>, <span class="py-src-parameter">latex</span>.<span class="py-src-parameter">SectionLatexSpitter</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyChapterLatexSpitter</span>(<span class="py-src-parameter">MySpitterMixin</span>, <span class="py-src-parameter">latex</span>.<span class="py-src-parameter">ChapterLatexSpitter</span>):
+ <span class="py-src-keyword">pass</span>
+</pre><div class="caption">spitters.py - <a href="listings/lore/spitters.py-2"><span class="filename">listings/lore/spitters.py-2</span></a></div></div></li>
+</ul>
+
+
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/lore/howto/index.html b/doc/lore/howto/index.html
new file mode 100644
index 0000000..9fd0669
--- /dev/null
+++ b/doc/lore/howto/index.html
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Lore Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Lore Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+ <li><a href="lore.html" shape="rect">Lore documentation system</a></li>
+ <li><a href="extend-lore.html" shape="rect">Extending Lore</a></li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/lore/howto/listings/lore/1st_example.html b/doc/lore/howto/listings/lore/1st_example.html
new file mode 100644
index 0000000..11ff82c
--- /dev/null
+++ b/doc/lore/howto/listings/lore/1st_example.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <title>My First Example</title>
+ </head>
+ <body>
+ <h1>My First Example</h1>
+ <p>The Frabjulon <span class="productname">Limpet 2000</span>
+ is the <span class="marketinglie">industry-leading</span> aquatic
+ mollusc counter, bar none.</p>
+ </body>
+</html>
+
diff --git a/doc/lore/howto/listings/lore/a_lore_plugin.py b/doc/lore/howto/listings/lore/a_lore_plugin.py
new file mode 100644
index 0000000..c6ac1b4
--- /dev/null
+++ b/doc/lore/howto/listings/lore/a_lore_plugin.py
@@ -0,0 +1,11 @@
+
+from zope.interface import implements
+
+from twisted.plugin import IPlugin
+from twisted.lore.scripts.lore import IProcessor
+
+class MyHTML(object):
+ implements(IPlugin, IProcessor)
+
+ name = "myhtml"
+ moduleName = "myhtml.factory"
diff --git a/doc/lore/howto/listings/lore/factory.py-1 b/doc/lore/howto/listings/lore/factory.py-1
new file mode 100644
index 0000000..0030955
--- /dev/null
+++ b/doc/lore/howto/listings/lore/factory.py-1
@@ -0,0 +1,9 @@
+from twisted.lore import default
+from myhtml import spitters
+
+class MyProcessingFunctionFactory(default.ProcessingFunctionFactory):
+ latexSpitters={None: spitters.MyLatexSpitter,
+ }
+
+# initialize the global variable factory with an instance of your new factory
+factory=MyProcessingFunctionFactory()
diff --git a/doc/lore/howto/listings/lore/factory.py-2 b/doc/lore/howto/listings/lore/factory.py-2
new file mode 100644
index 0000000..c5e0319
--- /dev/null
+++ b/doc/lore/howto/listings/lore/factory.py-2
@@ -0,0 +1,20 @@
+from twisted.lore import default
+from myhtml import spitters
+
+class MyProcessingFunctionFactory(default.ProcessingFunctionFactory):
+ latexSpitters={None: spitters.MyLatexSpitter,
+ }
+
+ # redefine getLintChecker to validate our classes
+ def getLintChecker(self):
+ # use the default checker from parent
+ checker = lint.getDefaultChecker()
+ checker.allowedClasses = checker.allowedClasses.copy()
+ oldSpan = checker.allowedClasses['span']
+ checkfunc=lambda cl: oldSpan(cl) or cl in ['marketinglie',
+ 'productname']
+ checker.allowedClasses['span'] = checkfunc
+ return checker
+
+# initialize the global variable factory with an instance of your new factory
+factory=MyProcessingFunctionFactory()
diff --git a/doc/lore/howto/listings/lore/factory.py-3 b/doc/lore/howto/listings/lore/factory.py-3
new file mode 100644
index 0000000..85e0374
--- /dev/null
+++ b/doc/lore/howto/listings/lore/factory.py-3
@@ -0,0 +1,21 @@
+from twisted.lore import default
+from myhtml import spitters
+
+class MyProcessingFunctionFactory(default.ProcessingFunctionFactory):
+ # 1. add the keys "chapter" and "section" to latexSpitters to handle the
+ # --config chapter and --config section options
+ latexSpitters={None: spitters.MyLatexSpitter,
+ "section": spitters.MySectionLatexSpitter,
+ "chapter": spitters.MyChapterLatexSpitter,
+ }
+
+ def getLintChecker(self):
+ checker = lint.getDefaultChecker()
+ checker.allowedClasses = checker.allowedClasses.copy()
+ oldSpan = checker.allowedClasses['span']
+ checkfunc=lambda cl: oldSpan(cl) or cl in ['marketinglie',
+ 'productname']
+ checker.allowedClasses['span'] = checkfunc
+ return checker
+
+factory=MyProcessingFunctionFactory()
diff --git a/doc/lore/howto/listings/lore/spitters.py-1 b/doc/lore/howto/listings/lore/spitters.py-1
new file mode 100644
index 0000000..b17a0be
--- /dev/null
+++ b/doc/lore/howto/listings/lore/spitters.py-1
@@ -0,0 +1,18 @@
+from twisted.lore import latex
+from twisted.lore.latex import processFile
+import os.path
+
+class MyLatexSpitter(latex.LatexSpitter):
+ def visitNode_span_productname(self, node):
+ # start an underline section in LaTeX
+ self.writer('\\underline{')
+ # process the node and its children
+ self.visitNodeDefault(node)
+ # end the underline block
+ self.writer('}')
+
+ def visitNode_span_marketinglie(self, node):
+ # this example turns on more than one LaTeX effect at once
+ self.writer('\\begin{bf}\\begin{Large}')
+ self.visitNodeDefault(node)
+ self.writer('\\end{Large}\\end{bf}')
diff --git a/doc/lore/howto/listings/lore/spitters.py-2 b/doc/lore/howto/listings/lore/spitters.py-2
new file mode 100644
index 0000000..7108d6b
--- /dev/null
+++ b/doc/lore/howto/listings/lore/spitters.py-2
@@ -0,0 +1,26 @@
+from twisted.lore import latex
+from twisted.lore.latex import processFile
+import os.path
+
+# 2. Create a new mixin that does what the old MyLatexSpitter used to do:
+# process the new classes we defined
+class MySpitterMixin:
+ def visitNode_span_productname(self, node):
+ self.writer('\\underline{')
+ self.visitNodeDefault(node)
+ self.writer('}')
+
+ def visitNode_span_marketinglie(self, node):
+ self.writer('\\begin{bf}\\begin{Large}')
+ self.visitNodeDefault(node)
+ self.writer('\\end{Large}\\end{bf}')
+
+# 3. inherit from the mixin class for each of the three sub-spitters
+class MyLatexSpitter(MySpitterMixin, latex.LatexSpitter):
+ pass
+
+class MySectionLatexSpitter(MySpitterMixin, latex.SectionLatexSpitter):
+ pass
+
+class MyChapterLatexSpitter(MySpitterMixin, latex.ChapterLatexSpitter):
+ pass
diff --git a/doc/lore/howto/lore.html b/doc/lore/howto/lore.html
new file mode 100644
index 0000000..6df1de6
--- /dev/null
+++ b/doc/lore/howto/lore.html
@@ -0,0 +1,369 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using the Lore Documentation System</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using the Lore Documentation System</h1>
+ <div class="toc"><ol><li><a href="#auto0">Writing Lore Documents</a></li><ul><li><a href="#auto1">Overview</a></li><li><a href="#auto2">Elements and Their Uses</a></li></ul><li><a href="#auto3">Writing Lore XHTML Templates</a></li><li><a href="#auto4">Using Lore to Generate HTML</a></li><li><a href="#auto5">Using Lore to Generate LaTex</a></li><ul><li><a href="#auto6">Articles</a></li><li><a href="#auto7">Books</a></li></ul><li><a href="#auto8">Using Lore to Generate Slides</a></li><ul><li><a href="#auto9">Magic Point Output</a></li><li><a href="#auto10">LaTeX Output</a></li></ul><li><a href="#auto11">Linting</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Writing Lore Documents<a name="auto0"/></h2>
+
+<h3>Overview<a name="auto1"/></h3>
+
+<p>Lore documents are a special subset of XHTML documents. They use specific
+subset of XHTML, together with custom classes, to allow a wide variety of
+document elements, including some Python-specific ones. Lore documents, in
+particular, are well-formed XML documents. XML can be written using a wide
+variety of tools: from run of the mill editors such as vi, through editors
+with XML help like EMACS and ending with XML specific tools like (need name
+of XML editor here). Here, we will not cover the specifics of writing XML
+documents, except for a very broad overview.</p>
+
+<p>XML documents contain elements, which are delimited by an opening
+tag which looks like <code>&lt;tag-name attribute=&quot;value&quot;&gt;</code>
+and ends with a closing tag, which looks
+like <code>&lt;/tag-name&gt;</code>. If an elements happen to contain
+nothing, it can be shortened to <code>&lt;tag-name
+/&gt;</code>. Elements can contain other elements, or text. Text can
+contain any characters except &lt;, &gt; and &amp;. These characters
+are rendered by &amp;lt;, &amp;gt; and &amp;amp;, respectively.</p>
+
+<p>A Lore document is a single <code>html</code> element. Inside this
+element, there are exactly two top-level elements: <code>head</code>
+and <code>body</code>. The <code>head</code> element must contain
+exactly one element: <code>title</code>, containing the title of the
+document. Most of the document will be contained in
+the <code>body</code> element. The <code>body</code> element must
+start with an <code>h1</code> (top-level header) element, which
+contains the exact same content as the <code>title</code> element.</p>
+
+<p>Thus, a fairly minimal Lore document might look like:</p>
+
+<pre xml:space="preserve">
+&lt;html&gt;
+&lt;head&gt;&lt;title&gt;Title&lt;/title&gt;&lt;/head&gt;
+&lt;body&gt;&lt;h1&gt;Title&lt;/h1&gt;&lt;/body&gt;
+&lt;/html&gt;
+</pre>
+
+<h3>Elements and Their Uses<a name="auto2"/></h3>
+
+<table border="2" cellpadding="7" cellspacing="0">
+<tr>
+<th colspan="1" rowspan="1">Element</th>
+<th colspan="1" rowspan="1">Description</th>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>p</code></td>
+<td colspan="1" rowspan="1">The paragraph element. Most of the document should be inside paragraphs.
+</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>span</code></td>
+<td colspan="1" rowspan="1">The span element is an element which has no meaning -- unless it has a
+special <code>class</code> attributes. The following classes have the stated
+meanings:
+<dl>
+<dt><code>footnote</code></dt>
+<dd>a small comment which should not be inside the main text-flow.</dd>
+<dt><code>manhole-output</code></dt>
+<dd>This signifies, within a manhole transcript, that the enclosed text is
+ the output and not something the user has to input.</dd>
+<dt><code>index</code></dt>
+<dd>This should be an <em>empty</em> element, with an attribute
+ <code>value</code>. That attribute should be an index term, in the
+ format of <code>generic!specific!more specific</code>. Usually,
+ you will only have one level, in which case <code>value=&quot;term&quot;</code>
+ works.</dd>
+</dl></td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>div</code></td>
+<td colspan="1" rowspan="1">The div element is equivalent to a span, except it always appears outside
+paragraphs. The following classes have the given meanings:
+<dl>
+<dt><code>note</code></dt>
+<dd>A short note which is not necessary for the understanding of the text.</dd>
+<dt><code>doit</code></dt>
+<dd>An indication that the discussed feature is not complete or implemented
+ yet.</dd>
+<dt><code>boxed</code></dt>
+<dd>An indication that the text should be clearly separated from its
+ surroundings.</dd>
+</dl></td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>a</code></td>
+<td colspan="1" rowspan="1">This element can have several meanings, depending on the attributes:
+<dl>
+<dt><code>name</code> attribute</dt>
+<dd>Add a label to the current position, which might be used in this document
+ or other documents to refer to.</dd>
+<dt><code>href=URL</code></dt>
+<dd>Refer to some WWW resource.</dd>
+<dt><code>href=relative-path</code>, <code>href=relative-path#label</code> or
+ <code>href=#label</code></dt>
+<dd>Refer to a position in a Lore resource. By default, relative links to
+ <code>.xhtml</code> files are changed to point to a <code>.html</code> file.
+ If you need a link to a local non-Lore .xhtml file, use
+ <code>class=absolute</code> to make Lore treat it as an absolute link.</dd>
+<dt><code>href=relative-path</code> with <code>class=py-listing</code> or
+ <code>class=html-listing</code></dt>
+<dd>Indicate the given resource is a part of the text flow, and should be
+ inlined (and if possible, syntax highlighted).</dd>
+</dl></td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>ol</code>, <code>ul</code></td>
+<td colspan="1" rowspan="1">A list. It can be enumerated or bulleted. Inside a list, the
+element <code>li</code> (for a list element) is valid.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>h2</code>, <code>h3</code></td>
+<td colspan="1" rowspan="1">Second- and third-level section headings.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>code</code></td>
+<td colspan="1" rowspan="1">A string which has meaning to the computer. There are many possible
+classes:
+<dl>
+<dt><code>API</code></dt>
+<dd>A class, function or a module. It does not have to be a fully qualified
+ name -- but if it isn't, a <code>base</code> attribute is necessary.
+ <br/>Example:
+ <code>&lt;code class=&quot;API&quot; base=&quot;urllib&quot;&gt;urlencode&lt;code&gt;</code>.</dd>
+<dt><code>shell</code></dt>
+<dd>Shell (usually Bourne) code.</dd>
+<dt><code>python</code></dt>
+<dd>Python code.</dd>
+<dt><code>py-prototype</code></dt>
+<dd>Function prototype.</dd>
+<dt><code>py-filename</code></dt>
+<dd>Python file.</dd>
+<dt><code>py-src-string</code></dt>
+<dd>Python string.</dd>
+<dt><code>py-signature</code></dt>
+<dd>Function signature.</dd>
+<dt><code>py-src-parameter</code></dt>
+<dd>Parameter.</dd>
+<dt><code>py-src-identifier</code></dt>
+<dd>Identifier.</dd>
+<dt><code>py-src-keyword</code></dt>
+<dd>Keyword.</dd>
+</dl></td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>pre</code></td>
+<td colspan="1" rowspan="1">Preformatted text, usually for file listings. It can be used with
+the <code>python</code> class to indicate Python syntax
+coloring. Other possible classes are <code>shell</code> (to indicate a
+shell-transcript) or <code>python-interpreter</code> (to indicate an
+interactive interpreter transcript).</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>img</code></td>
+<td colspan="1" rowspan="1">Insert the image indicated by the <code>src</code> attribute.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>q</code></td>
+<td colspan="1" rowspan="1">The quote signs (<code>&quot;</code>) are not recommended
+except in preformatted or code environment. Instead, quote by using the
+<code>q</code> element which allows nested quotes and properly distinguishes
+opening quote from closing quote.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>em</code>, <code>strong</code></td>
+<td colspan="1" rowspan="1">Emphasise (or strongly emphasise) text.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>table</code></td>
+<td colspan="1" rowspan="1">Tabular data. Inside a table, use the <code>tr</code>
+element for each rows, and inside it use either <code>td</code> for a regular
+table cell or <code>th</code> for a table header (column or row).</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>blockquote</code></td>
+<td colspan="1" rowspan="1">A long quote which should be properly seperated from the main text.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>cite</code></td>
+<td colspan="1" rowspan="1">Cite a resource.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>sub</code>, <code>sup</code></td>
+<td colspan="1" rowspan="1">Subscripts and superscripts.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>link</code></td>
+<td colspan="1" rowspan="1">Currently, the only <code>link</code> elements supported
+are for for indicating authorship. <code>&lt;link rel=&quot;author&quot;
+href=&quot;author-address@examples.com&quot; title=&quot;Author Name&quot; /&gt;</code>
+should be used to indicate authorship. Multiple instances
+are allowed, and indicate shared authorship.</td>
+</tr>
+
+</table>
+
+<h2>Writing Lore XHTML Templates<a name="auto3"/></h2>
+
+<p>One of Lore's output formats is XHTML. Lore itself is very markup-light,
+but the output XHTML is much more markup intensive. Part of the auto-generated
+markup is directed by a special template.</p>
+
+<p>The output of Lore is inserted into template in the following way:</p>
+
+<ul>
+<li>The title is appended into each element with class <code>title</code>.</li>
+<li>The body is inserted into the first element that has class
+ <code>body</code>.</li>
+<li>The table of contents is inserted into the first element that has class
+ <code>toc</code>.</li>
+</ul>
+
+<p>In particular, most of the header is not tampered with -- so it is
+easy to indicate a CSS stylesheet in the template.</p>
+
+<h2>Using Lore to Generate HTML<a name="auto4"/></h2>
+
+<p>After having written a template, the easiest way to build HTML from the Lore
+document is by:</p>
+
+<pre class="shell" xml:space="preserve">
+% lore --config template=mytemplate.tpl mydocument.xhtml
+</pre>
+
+<p>This will create a file called <code class="shell">mydocument.html</code>.
+</p>
+
+<p>For example, to generate the HTML version of the Twisted docs from a SVN
+checkout, do:</p>
+
+<pre class="shell" xml:space="preserve">
+% lore --config template=doc/core/howto/template.tpl doc/core/howto/*.xhtml
+</pre>
+
+<p>
+In order to generate files with a different extension, use the <code class="shell">--config</code> commandline flag to tell the HTML output plugin to
+use a different extension:
+</p>
+<pre class="shell" xml:space="preserve">
+% lore --config ext=.html doc/core/howto/*.xhtml
+</pre>
+<h2>Using Lore to Generate LaTex<a name="auto5"/></h2>
+
+<h3>Articles<a name="auto6"/></h3>
+
+<pre class="shell" xml:space="preserve">
+% lore --output latex mydocument.xhtml
+</pre>
+
+<h3>Books<a name="auto7"/></h3>
+
+<p>Have a Lore file for each section. Then, have a LaTeX file which inputs
+all the given LaTeX files. Generate all the LaTeX files by using</p>
+
+<pre class="shell" xml:space="preserve">
+% lore --output latex --config section *.xhtml
+</pre>
+
+<p>in the relevant directory.</p>
+
+<h2>Using Lore to Generate Slides<a name="auto8"/></h2>
+
+<p>Lore can also be used to generate slides for presentations. The start
+of a new slide is indicated by use of an h2 tag, with the content
+between the opening and closing tags the title of the slide. Slides
+are generated by</p>
+
+<pre class="shell" xml:space="preserve">
+% lore --input lore-slides myslides.xhtml
+</pre>
+
+<p>This, by default, will produce HTML output with one HTML file for
+each slide. For our example, the files would be named
+myslides-&lt;number&gt;.html, where number is the slide number,
+starting with 0 for the title slide. Lore will look for a template
+file, either indicated by the <code>--config
+template=mytemplate.tpl</code> or the default template.tpl in the
+current directory. An example slide template is found
+in <code>doc/examples/slides-template.tpl</code></p>
+
+<p>The slides module currently supports three major output types:
+HTML, Magic Point, and LaTeX. The options for the latter two will be
+covered individually.</p>
+
+<h3>Magic Point Output<a name="auto9"/></h3>
+
+<p>Lore supports outputting to the Magic Point file format.
+Magicpoint is a presentation program for X, which can be installed on
+Debian by <code>apt-get install mgp</code> or by visiting <a href="http://member.wide.ad.jp/wg/mgp/" shape="rect">the Magic Point homepage</a>
+otherwise. A template file is required, <code>template.mgp</code> is
+shipped in the <code>twisted/lore</code> directory. Magic Point
+slides are generated by </p>
+
+<pre class="shell" xml:space="preserve">
+% lore --input lore-slides --output mgp \
+ --config template=~/Twisted/twisted/lore/template.mgp \
+ myslides.xhtml
+</pre>
+
+<p>That will produce <code>myslides.mgp</code>.</p>
+
+<h3>LaTeX Output<a name="auto10"/></h3>
+
+<p>Lore can also produce slides in LaTeX format. It supports three
+main styles: one slide per page, two per page, and Prosper format,
+with the <code>--config</code> parameters
+being <code>page</code>, <code>twopage</code>,
+and <code>prosper</code> respectively. Prosper is a LaTeX class for
+creating slides, which can be installed on Debian by <code>apt-get
+install prosper</code> or by
+visiting <a href="http://sourceforge.net/projects/prosper/" shape="rect">the
+Prosper SourceForge page</a>. LaTeX format slides (using the Prosper
+option, for example) are generated by</p>
+
+<pre class="shell" xml:space="preserve">
+% lore --input lore-slides --output latex \
+ --config prosper myslides.xhtml
+</pre>
+
+<p> This will generate <code>myslides.tex</code> file that can be processed
+with <code>latex</code> or <code>pdftex</code> or the appropriate
+LaTeX processing command.</p>
+
+<h2>Linting<a name="auto11"/></h2>
+
+<pre xml:space="preserve">
+% lore --output lint mydocument.xhtml
+</pre>
+
+<p>This will generate compiler-style (file:line:column:message) warnings.
+It is possible to integrate these warnings into a smart editor such as
+EMACS, but it has not been done yet.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/lore/img/myhtml-output.png b/doc/lore/img/myhtml-output.png
new file mode 100644
index 0000000..4a00fbf
--- /dev/null
+++ b/doc/lore/img/myhtml-output.png
Binary files differ
diff --git a/doc/lore/index.html b/doc/lore/index.html
new file mode 100644
index 0000000..4f5f8af
--- /dev/null
+++ b/doc/lore/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Lore Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Lore Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Lore to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Lore</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/lore/man/lore-man.html b/doc/lore/man/lore-man.html
new file mode 100644
index 0000000..0e34ccc
--- /dev/null
+++ b/doc/lore/man/lore-man.html
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: GENERATELORE.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">GENERATELORE.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">DESCRIPTION</a></li><li><a href="#auto4">AUTHOR</a></li><li><a href="#auto5">REPORTING BUGS</a></li><li><a href="#auto6">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>lore - convert documentations formats
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>lore</strong> [-l <em>linkrel</em>] [-d <em>docsdir</em>] [-i <em>input</em>] [-o <em>output</em>] [--config attribute[=value] [...]] [-p] [file [...]]</p>
+
+<p><strong>lore</strong> --help</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>The <strong>--help</strong> prints out a usage message to standard output.
+<dl><dt><strong>-p</strong>, <strong>--plain</strong>
+</dt><dd>Use non-flashy progress bar - one file per line.
+</dd>
+
+<dt><strong>-n</strong>, <strong>--null</strong>
+</dt><dd>Do not report progress at all.
+</dd>
+
+<dt><strong>-N</strong>, <strong>--number</strong>
+</dt><dd>Add chapter/section numbers to section headings.
+</dd>
+
+<dt><em>-l</em>, <em>--linkrel</em>
+</dt><dd>Where non-document links should be relative to.
+</dd>
+
+<dt><em>-d</em>, <em>--docsdir</em>
+</dt><dd>Where to look for <strong>.html</strong> files if no files are given.
+</dd>
+
+<dt><em>-e</em>, <em>--inputext</em> &lt;extension&gt;
+</dt><dd>The extension that your Lore input files have (default: .xhtml)
+</dd>
+
+<dt><em>-i</em>, <em>--input</em>
+</dt><dd>Input format. New input formats can be dynamically registered. Lore itself
+comes with <q>lore</q> (the standard format), <q>mlore</q> (allows LaTeX equations)
+and <q>man</q> (man page format). If the input format is not registered as a plugin,
+a module of the named input will be searched. For example,
+<strong>--i</strong> twisted.lore.defaultis equivalent to using the default Lore input.
+</dd>
+
+<dt><em>-o</em>, <em>--output</em>
+</dt><dd>Output format. Available output formats depend on the input. For the core
+formats, lore and mlore support html, latex and lint, while man allows
+lore.
+</dd>
+
+<dt><em>-x</em>, <em>--index</em> &lt;filename&gt;
+</dt><dd>The base filename you want to give your index file.
+</dd>
+
+<dt><em>-b</em>, <em>--book</em> &lt;filename&gt;
+</dt><dd>The book file to generate a book from.
+</dd>
+
+<dt><em>--prefixurl</em> &lt;prefix&gt;
+</dt><dd>The prefix to stick on to relative links; only useful when processing
+directories.
+</dd>
+
+<dt><em>--version</em>
+</dt><dd>Display version information and exit.
+</dd>
+
+<dt><em>--config</em>
+</dt><dd>Add input/output-specific information.
+HTML output allows for 'ext=&lt;extension&gt;',
+'template=&lt;template&gt;' and 'baseurl=&lt;format string for API URLs&gt;'. LaTeX
+output allows for 'section' or 'chapter' in Lore, and nothing in Math-Lore.
+Lore output allows for 'ext=&lt;extension&gt;'. Lint output allows nothing.
+Note that disallowed <em>--config</em> options are merely ignored, and do
+not cause errors.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>DESCRIPTION<a name="auto3"/></h2>
+
+<p>If no files are given, all *.html documents in docsdir are processed.
+</p>
+
+<h2>AUTHOR<a name="auto4"/></h2>
+
+<p>Written by Moshe Zadka
+</p>
+
+<h2>REPORTING BUGS<a name="auto5"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto6"/></h2>
+
+<p>Copyright © 2003-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/lore/man/lore.1 b/doc/lore/man/lore.1
new file mode 100644
index 0000000..0d9cba1
--- /dev/null
+++ b/doc/lore/man/lore.1
@@ -0,0 +1,74 @@
+.TH GENERATELORE "1" "October 2002" "" ""
+.SH NAME
+lore \- convert documentations formats
+.SH SYNOPSIS
+.B lore [-l \fIlinkrel\fR] [-d \fIdocsdir\fR] [-i \fIinput\fR] [-o \fIoutput\fR] [--config attribute[=value] [...]] [-p] [file [...]]
+.PP
+.B lore --help
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB-p\fR, \fB--plain\fR
+Use non-flashy progress bar \- one file per line.
+.TP
+\fB-n\fR, \fB--null\fR
+Do not report progress at all.
+.TP
+\fB-N\fR, \fB--number\fR
+Add chapter/section numbers to section headings.
+.TP
+\fI-l\fR, \fI--linkrel\fR
+Where non-document links should be relative to.
+.TP
+\fI-d\fR, \fI--docsdir\fR
+Where to look for \fB.html\fR files if no files are given.
+.TP
+\fI-e\fR, \fI--inputext\fR <extension>
+The extension that your Lore input files have (default: .xhtml)
+.TP
+\fI-i\fR, \fI--input\fR
+Input format. New input formats can be dynamically registered. Lore itself
+comes with "lore" (the standard format), "mlore" (allows LaTeX equations)
+and "man" (man page format). If the input format is not registered as a plugin,
+a module of the named input will be searched. For example,
+.B --i twisted.lore.default
+is equivalent to using the default Lore input.
+.TP
+\fI-o\fR, \fI--output\fR
+Output format. Available output formats depend on the input. For the core
+formats, lore and mlore support html, latex and lint, while man allows
+lore.
+.TP
+\fI-x\fR, \fI--index\fR <filename>
+The base filename you want to give your index file.
+.TP
+\fI-b\fR, \fI--book\fR <filename>
+The book file to generate a book from.
+.TP
+\fI--prefixurl\fR <prefix>
+The prefix to stick on to relative links; only useful when processing
+directories.
+.TP
+\fI--version\fR
+Display version information and exit.
+.TP
+\fI--config\fR
+Add input/output-specific information.
+HTML output allows for 'ext=<extension>',
+'template=<template>' and 'baseurl=<format string for API URLs>'. LaTeX
+output allows for 'section' or 'chapter' in Lore, and nothing in Math-Lore.
+Lore output allows for 'ext=<extension>'. Lint output allows nothing.
+Note that disallowed \fI--config\fR options are merely ignored, and do
+not cause errors.
+.SH DESCRIPTION
+If no files are given, all *.html documents in docsdir are processed.
+.SH AUTHOR
+Written by Moshe Zadka
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2003-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/mail/examples/emailserver.tac b/doc/mail/examples/emailserver.tac
new file mode 100644
index 0000000..d769b88
--- /dev/null
+++ b/doc/mail/examples/emailserver.tac
@@ -0,0 +1,107 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this module directly with:
+# twistd -ny emailserver.tac
+
+"""
+A toy email server.
+"""
+
+from zope.interface import implements
+
+from twisted.internet import defer
+from twisted.mail import smtp
+from twisted.mail.imap4 import LOGINCredentials, PLAINCredentials
+
+from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+from twisted.cred.portal import IRealm
+from twisted.cred.portal import Portal
+
+
+
+class ConsoleMessageDelivery:
+ implements(smtp.IMessageDelivery)
+
+ def receivedHeader(self, helo, origin, recipients):
+ return "Received: ConsoleMessageDelivery"
+
+
+ def validateFrom(self, helo, origin):
+ # All addresses are accepted
+ return origin
+
+
+ def validateTo(self, user):
+ # Only messages directed to the "console" user are accepted.
+ if user.dest.local == "console":
+ return lambda: ConsoleMessage()
+ raise smtp.SMTPBadRcpt(user)
+
+
+
+class ConsoleMessage:
+ implements(smtp.IMessage)
+
+ def __init__(self):
+ self.lines = []
+
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+
+ def eomReceived(self):
+ print "New message received:"
+ print "\n".join(self.lines)
+ self.lines = None
+ return defer.succeed(None)
+
+
+ def connectionLost(self):
+ # There was an error, throw away the stored lines
+ self.lines = None
+
+
+
+class ConsoleSMTPFactory(smtp.SMTPFactory):
+ protocol = smtp.ESMTP
+
+ def __init__(self, *a, **kw):
+ smtp.SMTPFactory.__init__(self, *a, **kw)
+ self.delivery = ConsoleMessageDelivery()
+
+
+ def buildProtocol(self, addr):
+ p = smtp.SMTPFactory.buildProtocol(self, addr)
+ p.delivery = self.delivery
+ p.challengers = {"LOGIN": LOGINCredentials, "PLAIN": PLAINCredentials}
+ return p
+
+
+
+class SimpleRealm:
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if smtp.IMessageDelivery in interfaces:
+ return smtp.IMessageDelivery, ConsoleMessageDelivery(), lambda: None
+ raise NotImplementedError()
+
+
+
+def main():
+ from twisted.application import internet
+ from twisted.application import service
+
+ portal = Portal(SimpleRealm())
+ checker = InMemoryUsernamePasswordDatabaseDontUse()
+ checker.addUser("guest", "password")
+ portal.registerChecker(checker)
+
+ a = service.Application("Console SMTP Server")
+ internet.TCPServer(2500, ConsoleSMTPFactory(portal)).setServiceParent(a)
+
+ return a
+
+application = main()
diff --git a/doc/mail/examples/imap4client.py b/doc/mail/examples/imap4client.py
new file mode 100644
index 0000000..ee1bd44
--- /dev/null
+++ b/doc/mail/examples/imap4client.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Simple IMAP4 client which displays the subjects of all messages in a
+particular mailbox.
+"""
+
+import sys
+
+from twisted.internet import protocol
+from twisted.internet import ssl
+from twisted.internet import defer
+from twisted.internet import stdio
+from twisted.mail import imap4
+from twisted.protocols import basic
+from twisted.python import util
+from twisted.python import log
+
+class TrivialPrompter(basic.LineReceiver):
+ from os import linesep as delimiter
+
+ promptDeferred = None
+
+ def prompt(self, msg):
+ assert self.promptDeferred is None
+ self.display(msg)
+ self.promptDeferred = defer.Deferred()
+ return self.promptDeferred
+
+ def display(self, msg):
+ self.transport.write(msg)
+
+ def lineReceived(self, line):
+ if self.promptDeferred is None:
+ return
+ d, self.promptDeferred = self.promptDeferred, None
+ d.callback(line)
+
+class SimpleIMAP4Client(imap4.IMAP4Client):
+ greetDeferred = None
+
+ def serverGreeting(self, caps):
+ self.serverCapabilities = caps
+ if self.greetDeferred is not None:
+ d, self.greetDeferred = self.greetDeferred, None
+ d.callback(self)
+
+class SimpleIMAP4ClientFactory(protocol.ClientFactory):
+ usedUp = False
+
+ protocol = SimpleIMAP4Client
+
+ def __init__(self, username, onConn):
+ self.ctx = ssl.ClientContextFactory()
+
+ self.username = username
+ self.onConn = onConn
+
+ def buildProtocol(self, addr):
+ assert not self.usedUp
+ self.usedUp = True
+
+ p = self.protocol(self.ctx)
+ p.factory = self
+ p.greetDeferred = self.onConn
+
+ auth = imap4.CramMD5ClientAuthenticator(self.username)
+ p.registerAuthenticator(auth)
+
+ return p
+
+ def clientConnectionFailed(self, connector, reason):
+ d, self.onConn = self.onConn, None
+ d.errback(reason)
+
+# Initial callback - invoked after the server sends us its greet message
+def cbServerGreeting(proto, username, password):
+ # Hook up stdio
+ tp = TrivialPrompter()
+ stdio.StandardIO(tp)
+
+ # And make it easily accessible
+ proto.prompt = tp.prompt
+ proto.display = tp.display
+
+ # Try to authenticate securely
+ return proto.authenticate(password
+ ).addCallback(cbAuthentication, proto
+ ).addErrback(ebAuthentication, proto, username, password
+ )
+
+# Fallback error-handler. If anything goes wrong, log it and quit.
+def ebConnection(reason):
+ log.startLogging(sys.stdout)
+ log.err(reason)
+ from twisted.internet import reactor
+ reactor.stop()
+
+# Callback after authentication has succeeded
+def cbAuthentication(result, proto):
+ # List a bunch of mailboxes
+ return proto.list("", "*"
+ ).addCallback(cbMailboxList, proto
+ )
+
+# Errback invoked when authentication fails
+def ebAuthentication(failure, proto, username, password):
+ # If it failed because no SASL mechanisms match, offer the user the choice
+ # of logging in insecurely.
+ failure.trap(imap4.NoSupportedAuthentication)
+ return proto.prompt("No secure authentication available. Login insecurely? (y/N) "
+ ).addCallback(cbInsecureLogin, proto, username, password
+ )
+
+# Callback for "insecure-login" prompt
+def cbInsecureLogin(result, proto, username, password):
+ if result.lower() == "y":
+ # If they said yes, do it.
+ return proto.login(username, password
+ ).addCallback(cbAuthentication, proto
+ )
+ return defer.fail(Exception("Login failed for security reasons."))
+
+# Callback invoked when a list of mailboxes has been retrieved
+def cbMailboxList(result, proto):
+ result = [e[2] for e in result]
+ s = '\n'.join(['%d. %s' % (n + 1, m) for (n, m) in zip(range(len(result)), result)])
+ if not s:
+ return defer.fail(Exception("No mailboxes exist on server!"))
+ return proto.prompt(s + "\nWhich mailbox? [1] "
+ ).addCallback(cbPickMailbox, proto, result
+ )
+
+# When the user selects a mailbox, "examine" it.
+def cbPickMailbox(result, proto, mboxes):
+ mbox = mboxes[int(result or '1') - 1]
+ return proto.examine(mbox
+ ).addCallback(cbExamineMbox, proto
+ )
+
+# Callback invoked when examine command completes.
+def cbExamineMbox(result, proto):
+ # Retrieve the subject header of every message on the mailbox.
+ return proto.fetchSpecific('1:*',
+ headerType='HEADER.FIELDS',
+ headerArgs=['SUBJECT']
+ ).addCallback(cbFetch, proto
+ )
+
+# Finally, display headers.
+def cbFetch(result, proto):
+ keys = result.keys()
+ keys.sort()
+ for k in keys:
+ proto.display('%s %s' % (k, result[k][0][2]))
+ return proto.logout()
+
+PORT = 143
+
+def main():
+ hostname = raw_input('IMAP4 Server Hostname: ')
+ username = raw_input('IMAP4 Username: ')
+ password = util.getPassword('IMAP4 Password: ')
+
+ onConn = defer.Deferred(
+ ).addCallback(cbServerGreeting, username, password
+ ).addErrback(ebConnection
+ )
+
+ factory = SimpleIMAP4ClientFactory(username, onConn)
+
+ from twisted.internet import reactor
+ conn = reactor.connectTCP(hostname, PORT, factory)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/mail/examples/index.html b/doc/mail/examples/index.html
new file mode 100644
index 0000000..19c8c63
--- /dev/null
+++ b/doc/mail/examples/index.html
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Mail code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Mail code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">SMTP servers</a></li><li><a href="#auto1">SMTP clients</a></li><li><a href="#auto2">IMAP clients</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>SMTP servers<a name="auto0"/></h2>
+ <ul>
+ <li><a href="emailserver.tac" shape="rect">emailserver.tac</a> - a toy email server.</li>
+ </ul>
+
+ <h2>SMTP clients<a name="auto1"/></h2>
+ <ul>
+ <li><a href="smtpclient_simple.py" shape="rect">smtpclient_simple.py</a> - sending email using SMTP.</li>
+ <li><a href="smtpclient_tls.py" shape="rect">smtpclient_tls.py</a> - send email
+ using authentication and transport layer security.</li>
+ </ul>
+
+ <h2>IMAP clients<a name="auto2"/></h2>
+ <ul>
+ <li><a href="imap4client.py" shape="rect">imap4client.py</a> - Simple IMAP4
+ client which displays the subjects of all messages in a
+ particular mailbox.</li>
+ </ul>
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/mail/examples/smtpclient_simple.py b/doc/mail/examples/smtpclient_simple.py
new file mode 100644
index 0000000..825d269
--- /dev/null
+++ b/doc/mail/examples/smtpclient_simple.py
@@ -0,0 +1,47 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Demonstrate sending mail via SMTP.
+"""
+
+import sys
+from email.mime.text import MIMEText
+
+from twisted.python import log
+from twisted.mail.smtp import sendmail
+from twisted.internet import reactor
+
+
+def send(message, subject, sender, recipients, host):
+ """
+ Send email to one or more addresses.
+ """
+ msg = MIMEText(message)
+ msg['Subject'] = subject
+ msg['From'] = sender
+ msg['To'] = ', '.join(recipients)
+
+ dfr = sendmail(host, sender, recipients, msg.as_string())
+ def success(r):
+ reactor.stop()
+ def error(e):
+ print e
+ reactor.stop()
+ dfr.addCallback(success)
+ dfr.addErrback(error)
+
+ reactor.run()
+
+
+if __name__ == '__main__':
+ msg = 'This is the message body'
+ subject = 'This is the message subject'
+
+ host = 'smtp.example.com'
+ sender = 'sender@example.com'
+ recipients = ['recipient@example.com']
+
+ log.startLogging(sys.stdout)
+ send(msg, subject, sender, recipients, host)
+
diff --git a/doc/mail/examples/smtpclient_tls.py b/doc/mail/examples/smtpclient_tls.py
new file mode 100644
index 0000000..758b97d
--- /dev/null
+++ b/doc/mail/examples/smtpclient_tls.py
@@ -0,0 +1,157 @@
+
+"""
+Demonstrate sending mail via SMTP while employing TLS and performing
+authentication.
+"""
+
+import sys
+
+from OpenSSL.SSL import SSLv3_METHOD
+
+from twisted.mail.smtp import ESMTPSenderFactory
+from twisted.python.usage import Options, UsageError
+from twisted.internet.ssl import ClientContextFactory
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
+
+def sendmail(
+ authenticationUsername, authenticationSecret,
+ fromAddress, toAddress,
+ messageFile,
+ smtpHost, smtpPort=25
+ ):
+ """
+ @param authenticationUsername: The username with which to authenticate.
+ @param authenticationSecret: The password with which to authenticate.
+ @param fromAddress: The SMTP reverse path (ie, MAIL FROM)
+ @param toAddress: The SMTP forward path (ie, RCPT TO)
+ @param messageFile: A file-like object containing the headers and body of
+ the message to send.
+ @param smtpHost: The MX host to which to connect.
+ @param smtpPort: The port number to which to connect.
+
+ @return: A Deferred which will be called back when the message has been
+ sent or which will errback if it cannot be sent.
+ """
+
+ # Create a context factory which only allows SSLv3 and does not verify
+ # the peer's certificate.
+ contextFactory = ClientContextFactory()
+ contextFactory.method = SSLv3_METHOD
+
+ resultDeferred = Deferred()
+
+ senderFactory = ESMTPSenderFactory(
+ authenticationUsername,
+ authenticationSecret,
+ fromAddress,
+ toAddress,
+ messageFile,
+ resultDeferred,
+ contextFactory=contextFactory)
+
+ reactor.connectTCP(smtpHost, smtpPort, senderFactory)
+
+ return resultDeferred
+
+
+
+class SendmailOptions(Options):
+ synopsis = "smtpclient_tls.py [options]"
+
+ optParameters = [
+ ('username', 'u', None,
+ 'The username with which to authenticate to the SMTP server.'),
+ ('password', 'p', None,
+ 'The password with which to authenticate to the SMTP server.'),
+ ('from-address', 'f', None,
+ 'The address from which to send the message.'),
+ ('to-address', 't', None,
+ 'The address to which to send the message.'),
+ ('message', 'm', None,
+ 'The filename which contains the message to send.'),
+ ('smtp-host', 'h', None,
+ 'The host through which to send the message.'),
+ ('smtp-port', None, '25',
+ 'The port number on smtp-host to which to connect.')]
+
+
+ def postOptions(self):
+ """
+ Parse integer parameters, open the message file, and make sure all
+ required parameters have been specified.
+ """
+ try:
+ self['smtp-port'] = int(self['smtp-port'])
+ except ValueError:
+ raise UsageError("--smtp-port argument must be an integer.")
+ if self['username'] is None:
+ raise UsageError(
+ "Must specify authentication username with --username")
+ if self['password'] is None:
+ raise UsageError(
+ "Must specify authentication password with --password")
+ if self['from-address'] is None:
+ raise UsageError("Must specify from address with --from-address")
+ if self['to-address'] is None:
+ raise UsageError("Must specify from address with --to-address")
+ if self['smtp-host'] is None:
+ raise UsageError("Must specify smtp host with --smtp-host")
+ if self['message'] is None:
+ raise UsageError(
+ "Must specify a message file to send with --message")
+ try:
+ self['message'] = file(self['message'])
+ except Exception, e:
+ raise UsageError(e)
+
+
+
+def cbSentMessage(result):
+ """
+ Called when the message has been sent.
+
+ Report success to the user and then stop the reactor.
+ """
+ print "Message sent"
+ reactor.stop()
+
+
+
+def ebSentMessage(err):
+ """
+ Called if the message cannot be sent.
+
+ Report the failure to the user and then stop the reactor.
+ """
+ err.printTraceback()
+ reactor.stop()
+
+
+
+def main(args=None):
+ """
+ Parse arguments and send an email based on them.
+ """
+ o = SendmailOptions()
+ try:
+ o.parseOptions(args)
+ except UsageError, e:
+ raise SystemExit(e)
+ else:
+ from twisted.python import log
+ log.startLogging(sys.stdout)
+ result = sendmail(
+ o['username'],
+ o['password'],
+ o['from-address'],
+ o['to-address'],
+ o['message'],
+ o['smtp-host'],
+ o['smtp-port'])
+ result.addCallbacks(cbSentMessage, ebSentMessage)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/doc/mail/index.html b/doc/mail/index.html
new file mode 100644
index 0000000..dd32774
--- /dev/null
+++ b/doc/mail/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Mail Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Mail Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Mail</li>
+<li><a href="tutorial/smtpclient/smtpclient.html" shape="rect">Twisted Mail Tutorial</a>: Building
+an SMTP Client from Scratch</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/mail/man/mailmail-man.html b/doc/mail/man/mailmail-man.html
new file mode 100644
index 0000000..57841a6
--- /dev/null
+++ b/doc/mail/man/mailmail-man.html
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: MAILMAIL.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">MAILMAIL.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>mailmail - Twisted sendmail compatibility script
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>mailmail</strong> [recipient addresses]</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>mailmail reads RFC822 message text from standard input and delivers them,
+using SMTP, to a Mail Transfer Agent listening at 127.0.0.1:25. It accepts
+(but does not necessarily implement) many of the standard sendmail(1)
+options, but it is preferable to list only the recipient addresses on
+the command line, and include a <strong>From</strong> header within the message text
+indicating the sender.
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Jp Calderone
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2003-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/mail/man/mailmail.1 b/doc/mail/man/mailmail.1
new file mode 100644
index 0000000..9bff0f7
--- /dev/null
+++ b/doc/mail/man/mailmail.1
@@ -0,0 +1,21 @@
+.TH MAILMAIL "1" "July 2003" "" ""
+.SH NAME
+mailmail \- Twisted sendmail compatibility script
+.SH SYNOPSIS
+.B mailmail [recipient addresses]
+.SH DESCRIPTION
+mailmail reads RFC822 message text from standard input and delivers them,
+using SMTP, to a Mail Transfer Agent listening at 127.0.0.1:25. It accepts
+(but does not necessarily implement) many of the standard sendmail(1)
+options, but it is preferable to list only the recipient addresses on
+the command line, and include a \fBFrom\fR header within the message text
+indicating the sender.
+.SH AUTHOR
+Written by Jp Calderone
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2003-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-1.tac b/doc/mail/tutorial/smtpclient/smtpclient-1.tac
new file mode 100644
index 0000000..40b685c
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-1.tac
@@ -0,0 +1,3 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-10.tac b/doc/mail/tutorial/smtpclient/smtpclient-10.tac
new file mode 100644
index 0000000..dcfe5ef
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-10.tac
@@ -0,0 +1,56 @@
+import StringIO
+
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.internet import defer
+from twisted.mail import smtp
+
+class SMTPTutorialClient(smtp.ESMTPClient):
+ mailFrom = "tutorial_sender@example.com"
+ mailTo = "tutorial_recipient@example.net"
+ mailData = '''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy <tutorial_sender@example.com>
+To: Tutorial Gal <tutorial_recipient@example.net>
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''
+
+ def getMailFrom(self):
+ result = self.mailFrom
+ self.mailFrom = None
+ return result
+
+ def getMailTo(self):
+ return [self.mailTo]
+
+ def getMailData(self):
+ return StringIO.StringIO(self.mailData)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ print 'Sent', numOk, 'messages'
+
+ from twisted.internet import reactor
+ reactor.stop()
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = SMTPTutorialClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+def getMailExchange(host):
+ return defer.succeed('localhost')
+
+def cbMailExchange(exchange):
+ smtpClientFactory = SMTPClientFactory()
+
+ smtpClientService = internet.TCPClient(exchange, 25, smtpClientFactory)
+ smtpClientService.setServiceParent(application)
+
+getMailExchange('example.net').addCallback(cbMailExchange)
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-11.tac b/doc/mail/tutorial/smtpclient/smtpclient-11.tac
new file mode 100644
index 0000000..a52a3eb
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-11.tac
@@ -0,0 +1,58 @@
+import StringIO
+
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.internet import defer
+from twisted.mail import smtp, relaymanager
+
+class SMTPTutorialClient(smtp.ESMTPClient):
+ mailFrom = "tutorial_sender@example.com"
+ mailTo = "tutorial_recipient@example.net"
+ mailData = '''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy <tutorial_sender@example.com>
+To: Tutorial Gal <tutorial_recipient@example.net>
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''
+
+ def getMailFrom(self):
+ result = self.mailFrom
+ self.mailFrom = None
+ return result
+
+ def getMailTo(self):
+ return [self.mailTo]
+
+ def getMailData(self):
+ return StringIO.StringIO(self.mailData)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ print 'Sent', numOk, 'messages'
+
+ from twisted.internet import reactor
+ reactor.stop()
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = SMTPTutorialClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+def getMailExchange(host):
+ def cbMX(mxRecord):
+ return str(mxRecord.name)
+ return relaymanager.MXCalculator().getMX(host).addCallback(cbMX)
+
+def cbMailExchange(exchange):
+ smtpClientFactory = SMTPClientFactory()
+
+ smtpClientService = internet.TCPClient(exchange, 25, smtpClientFactory)
+ smtpClientService.setServiceParent(application)
+
+getMailExchange('example.net').addCallback(cbMailExchange)
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-2.tac b/doc/mail/tutorial/smtpclient/smtpclient-2.tac
new file mode 100644
index 0000000..e95921b
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-2.tac
@@ -0,0 +1,10 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpClientFactory = protocol.ClientFactory()
+smtpClientService = internet.TCPClient(None, None, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-3.tac b/doc/mail/tutorial/smtpclient/smtpclient-3.tac
new file mode 100644
index 0000000..26ea519
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-3.tac
@@ -0,0 +1,10 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpClientFactory = protocol.ClientFactory()
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-4.tac b/doc/mail/tutorial/smtpclient/smtpclient-4.tac
new file mode 100644
index 0000000..e95e596
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-4.tac
@@ -0,0 +1,12 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpClientFactory = protocol.ClientFactory()
+smtpClientFactory.protocol = protocol.Protocol
+
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-5.tac b/doc/mail/tutorial/smtpclient/smtpclient-5.tac
new file mode 100644
index 0000000..30af8ad
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-5.tac
@@ -0,0 +1,14 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpClientFactory = protocol.ClientFactory()
+
+from twisted.mail import smtp
+smtpClientFactory.protocol = smtp.ESMTPClient
+
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-6.tac b/doc/mail/tutorial/smtpclient/smtpclient-6.tac
new file mode 100644
index 0000000..d1eb5a0
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-6.tac
@@ -0,0 +1,18 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.mail import smtp
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = smtp.ESMTPClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+smtpClientFactory = SMTPClientFactory()
+
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-7.tac b/doc/mail/tutorial/smtpclient/smtpclient-7.tac
new file mode 100644
index 0000000..297a35a
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-7.tac
@@ -0,0 +1,46 @@
+import StringIO
+
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.mail import smtp
+
+class SMTPTutorialClient(smtp.ESMTPClient):
+ mailFrom = "tutorial_sender@example.com"
+ mailTo = "tutorial_recipient@example.net"
+ mailData = '''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy <tutorial_sender@example.com>
+To: Tutorial Gal <tutorial_recipient@example.net>
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''
+
+ def getMailFrom(self):
+ result = self.mailFrom
+ self.mailFrom = None
+ return result
+
+ def getMailTo(self):
+ return [self.mailTo]
+
+ def getMailData(self):
+ return StringIO.StringIO(self.mailData)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ print 'Sent', numOk, 'messages'
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = SMTPTutorialClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+smtpClientFactory = SMTPClientFactory()
+
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-8.tac b/doc/mail/tutorial/smtpclient/smtpclient-8.tac
new file mode 100644
index 0000000..8dbef10
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-8.tac
@@ -0,0 +1,49 @@
+import StringIO
+
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.mail import smtp
+
+class SMTPTutorialClient(smtp.ESMTPClient):
+ mailFrom = "tutorial_sender@example.com"
+ mailTo = "tutorial_recipient@example.net"
+ mailData = '''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy <tutorial_sender@example.com>
+To: Tutorial Gal <tutorial_recipient@example.net>
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''
+
+ def getMailFrom(self):
+ result = self.mailFrom
+ self.mailFrom = None
+ return result
+
+ def getMailTo(self):
+ return [self.mailTo]
+
+ def getMailData(self):
+ return StringIO.StringIO(self.mailData)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ print 'Sent', numOk, 'messages'
+
+ from twisted.internet import reactor
+ reactor.stop()
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = SMTPTutorialClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+smtpClientFactory = SMTPClientFactory()
+
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpclient/smtpclient-9.tac b/doc/mail/tutorial/smtpclient/smtpclient-9.tac
new file mode 100644
index 0000000..397057a
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient-9.tac
@@ -0,0 +1,53 @@
+import StringIO
+
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.mail import smtp
+
+class SMTPTutorialClient(smtp.ESMTPClient):
+ mailFrom = "tutorial_sender@example.com"
+ mailTo = "tutorial_recipient@example.net"
+ mailData = '''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy <tutorial_sender@example.com>
+To: Tutorial Gal <tutorial_recipient@example.net>
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''
+
+ def getMailFrom(self):
+ result = self.mailFrom
+ self.mailFrom = None
+ return result
+
+ def getMailTo(self):
+ return [self.mailTo]
+
+ def getMailData(self):
+ return StringIO.StringIO(self.mailData)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ print 'Sent', numOk, 'messages'
+
+ from twisted.internet import reactor
+ reactor.stop()
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = SMTPTutorialClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+def getMailExchange(host):
+ return 'localhost'
+
+smtpClientFactory = SMTPClientFactory()
+
+smtpClientService = internet.TCPClient(
+ getMailExchange('example.net'), 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpclient/smtpclient.html b/doc/mail/tutorial/smtpclient/smtpclient.html
new file mode 100644
index 0000000..954037e
--- /dev/null
+++ b/doc/mail/tutorial/smtpclient/smtpclient.html
@@ -0,0 +1,757 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Mail Tutorial: Building an SMTP Client from Scratch</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Mail Tutorial: Building an SMTP Client from Scratch</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><ul><li><a href="#auto1">SMTP Client 1</a></li><li><a href="#auto2">SMTP Client 2</a></li><li><a href="#auto3">SMTP Client 3</a></li><li><a href="#auto4">SMTP Client 4</a></li><li><a href="#auto5">SMTP Client 5</a></li><li><a href="#auto6">SMTP Client 6</a></li><li><a href="#auto7">SMTP Client 7</a></li><li><a href="#auto8">SMTP Client 8</a></li><li><a href="#auto9">SMTP Client 9</a></li><li><a href="#auto10">SMTP Client 10</a></li><li><a href="#auto11">SMTP Client 11</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p>This tutorial will walk you through the creation of an extremely
+simple SMTP client application. By the time the tutorial is complete,
+you will understand how to create and start a TCP client speaking the
+SMTP protocol, have it connect to an appropriate mail exchange server,
+and transmit a message for delivery.</p>
+
+<p>For the majority of this tutorial, <code>twistd</code> will be used
+to launch the application. Near the end we will explore other
+possibilities for starting a Twisted application. Until then, make
+sure that you have <code>twistd</code> installed and conveniently
+accessible for use in running each of the example <code>.tac</code>
+files.</p>
+
+<h3>SMTP Client 1<a name="auto1"/></h3>
+
+<p>The first step is to create <a href="smtpclient-1.tac" shape="rect">the most
+minimal <code>.tac</code> file</a> possible for use by <code>twistd</code> .</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>
+</pre>
+
+<p>The first line of the <code>.tac</code> file
+imports <code>twisted.application.service</code>, a module which
+contains many of the basic <em>service</em> classes and helper
+functions available in Twisted. In particular, we will be using
+the <code>Application</code> function to create a new <em>application
+service</em>. An <em>application service</em> simply acts as a
+central object on which to store certain kinds of deployment
+configuration.</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;SMTP Client Tutorial&quot;</span>)
+</pre>
+
+<p>The second line of the <code>.tac</code> file creates a
+new <em>application service</em> and binds it to the local
+name <code>application</code>. <code>twistd</code> requires this
+local name in each <code>.tac</code> file it runs. It uses various
+pieces of configuration on the object to determine its behavior. For
+example, <code>&quot;SMTP Client Tutorial&quot;</code> will be used as the name
+of the <code>.tap</code> file into which to serialize application
+state, should it be necessary to do so.</p>
+
+<p>That does it for the first example. We now have enough of
+a <code>.tac</code> file to pass to <code>twistd</code>. If we
+run <a href="smtpclient-1.tac" shape="rect">smtpclient-1.tac</a> using
+the <code>twistd</code> command line:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">twistd</span> -<span class="py-src-variable">ny</span> <span class="py-src-variable">smtpclient</span>-<span class="py-src-number">1.</span><span class="py-src-variable">tac</span>
+</pre>
+
+<p>we are rewarded with the following output:</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/mail/tutorial/smtpclient$ twistd -ny smtpclient-1.tac
+18:31 EST [-] Log opened.
+18:31 EST [-] twistd 2.0.0 (/usr/bin/python2.4 2.4.1) starting up
+18:31 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+18:31 EST [-] Loading smtpclient-1.tac...
+18:31 EST [-] Loaded.
+</pre>
+
+<p>As we expected, not much is going on. We can shutdown this server
+by issuing <code>^C</code>:</p>
+
+<pre class="shell" xml:space="preserve">
+18:34 EST [-] Received SIGINT, shutting down.
+18:34 EST [-] Main loop terminated.
+18:34 EST [-] Server Shut Down.
+exarkun@boson:~/mail/tutorial/smtpclient$
+</pre>
+
+<h3>SMTP Client 2<a name="auto2"/></h3>
+
+<p>The first version of our SMTP client wasn't very interesting. It
+didn't even establish any TCP connections! The <a href="smtpclient-2.tac" shape="rect">second version</a> will come a little bit
+closer to that level of complexity. First, we need to import a few
+more things:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
+</pre>
+
+<p><code>twisted.application.internet</code> is
+another <em>application service</em> module. It provides services for
+establishing outgoing connections (as well as creating network
+servers, though we are not interested in those parts for the
+moment). <code>twisted.internet.protocol</code> provides base
+implementations of many of the core Twisted concepts, such
+as <em>factories</em> and <em>protocols</em>.</p>
+
+<p>The next line of <a href="smtpclient-2.tac" shape="rect">smtpclient-2.tac</a>
+instantiates a new <em>client factory</em>.</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientFactory</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ClientFactory</span>()
+</pre>
+
+<p><em>Client factories</em> are responsible for
+constructing <em>protocol instances</em> whenever connections are
+established. They may be required to create just one instance, or
+many instances if many different connections are established, or they
+may never be required to create one at all, if no connection ever
+manages to be established.</p>
+
+<p>Now that we have a client factory, we'll need to hook it up to the
+network somehow. The next line of <code>smtpclient-2.tac</code> does
+just that:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">smtpClientFactory</span>)
+</pre>
+
+<p>We'll ignore the first two arguments
+to <code>internet.TCPClient</code> for the moment and instead focus on
+the third. <code>TCPClient</code> is one of those <em>application
+service</em> classes. It creates TCP connections to a specified
+address and then uses its third argument, a <em>client factory</em>,
+to get a <em>protocol instance</em>. It then associates the TCP
+connection with the protocol instance and gets out of the way.</p>
+
+<p>We can try to run <code>smtpclient-2.tac</code> the same way we
+ran <code>smtpclient-1.tac</code>, but the results might be a little
+disappointing:</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/mail/tutorial/smtpclient$ twistd -ny smtpclient-2.tac
+18:55 EST [-] Log opened.
+18:55 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+18:55 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+18:55 EST [-] Loading smtpclient-2.tac...
+18:55 EST [-] Loaded.
+18:55 EST [-] Starting factory &lt;twisted.internet.protocol.ClientFactory
+ instance at 0xb791e46c&gt;
+18:55 EST [-] Traceback (most recent call last):
+ File &quot;twisted/scripts/twistd.py&quot;, line 187, in runApp
+ app.runReactorWithLogging(config, oldstdout, oldstderr)
+ File &quot;twisted/application/app.py&quot;, line 128, in runReactorWithLogging
+ reactor.run()
+ File &quot;twisted/internet/posixbase.py&quot;, line 200, in run
+ self.mainLoop()
+ File &quot;twisted/internet/posixbase.py&quot;, line 208, in mainLoop
+ self.runUntilCurrent()
+ --- &lt;exception caught here&gt; ---
+ File &quot;twisted/internet/base.py&quot;, line 533, in runUntilCurrent
+ call.func(*call.args, **call.kw)
+ File &quot;twisted/internet/tcp.py&quot;, line 489, in resolveAddress
+ if abstract.isIPAddress(self.addr[0]):
+ File &quot;twisted/internet/abstract.py&quot;, line 315, in isIPAddress
+ parts = string.split(addr, '.')
+ File &quot;/usr/lib/python2.4/string.py&quot;, line 292, in split
+ return s.split(sep, maxsplit)
+ exceptions.AttributeError: 'NoneType' object has no attribute 'split'
+
+18:55 EST [-] Received SIGINT, shutting down.
+18:55 EST [-] Main loop terminated.
+18:55 EST [-] Server Shut Down.
+exarkun@boson:~/mail/tutorial/smtpclient$
+</pre>
+
+<p>What happened? Those first two arguments to <code>TCPClient</code>
+turned out to be important after all. We'll get to them in the next
+example.</p>
+
+<h3>SMTP Client 3<a name="auto3"/></h3>
+
+<p>Version three of our SMTP client only changes one thing. The line
+from version two:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">smtpClientFactory</span>)
+</pre>
+
+<p>has its first two arguments changed from <code>None</code> to
+something with a bit more meaning:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">25</span>, <span class="py-src-variable">smtpClientFactory</span>)
+</pre>
+
+<p>This directs the client to connect to <em>localhost</em> on
+port <em>25</em>. This isn't the address we want ultimately, but it's
+a good place-holder for the time being. We can
+run <a href="smtpclient-3.tac" shape="rect">smtpclient-3.tac</a> and see what this
+change gets us:</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/mail/tutorial/smtpclient$ twistd -ny smtpclient-3.tac
+19:10 EST [-] Log opened.
+19:10 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+19:10 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+19:10 EST [-] Loading smtpclient-3.tac...
+19:10 EST [-] Loaded.
+19:10 EST [-] Starting factory &lt;twisted.internet.protocol.ClientFactory
+ instance at 0xb791e48c&gt;
+19:10 EST [-] Enabling Multithreading.
+19:10 EST [Uninitialized] Traceback (most recent call last):
+ File &quot;twisted/python/log.py&quot;, line 56, in callWithLogger
+ return callWithContext({&quot;system&quot;: lp}, func, *args, **kw)
+ File &quot;twisted/python/log.py&quot;, line 41, in callWithContext
+ return context.call({ILogContext: newCtx}, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 52, in callWithContext
+ return self.currentContext().callWithContext(ctx, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 31, in callWithContext
+ return func(*args,**kw)
+ --- &lt;exception caught here&gt; ---
+ File &quot;twisted/internet/selectreactor.py&quot;, line 139, in _doReadOrWrite
+ why = getattr(selectable, method)()
+ File &quot;twisted/internet/tcp.py&quot;, line 543, in doConnect
+ self._connectDone()
+ File &quot;twisted/internet/tcp.py&quot;, line 546, in _connectDone
+ self.protocol = self.connector.buildProtocol(self.getPeer())
+ File &quot;twisted/internet/base.py&quot;, line 641, in buildProtocol
+ return self.factory.buildProtocol(addr)
+ File &quot;twisted/internet/protocol.py&quot;, line 99, in buildProtocol
+ p = self.protocol()
+ exceptions.TypeError: 'NoneType' object is not callable
+
+19:10 EST [Uninitialized] Stopping factory
+ &lt;twisted.internet.protocol.ClientFactory instance at
+ 0xb791e48c&gt;
+19:10 EST [-] Received SIGINT, shutting down.
+19:10 EST [-] Main loop terminated.
+19:10 EST [-] Server Shut Down.
+exarkun@boson:~/mail/tutorial/smtpclient$
+</pre>
+
+<p>A meagre amount of progress, but the service still raises an
+exception. This time, it's because we haven't specified
+a <em>protocol class</em> for the factory to use. We'll do that in
+the next example.</p>
+
+<h3>SMTP Client 4<a name="auto4"/></h3>
+
+<p>In the previous example, we ran into a problem because we hadn't
+set up our <em>client factory's</em> <em>protocol</em> attribute
+correctly (or at all). <code>ClientFactory.buildProtocol</code> is
+the method responsible for creating a <em>protocol instance</em>. The
+default implementation calls the factory's <code>protocol</code> attribute,
+adds itself as an attribute named <code>factory</code> to the
+resulting instance, and returns it. In <a href="smtpclient-4.tac" shape="rect">smtpclient-4.tac</a>, we'll correct the
+oversight that caused the traceback in smtpclient-3.tac:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientFactory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">Protocol</span>
+</pre>
+
+<p>Running this version of the client, we can see the output is once
+again traceback free:</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-4.tac
+19:29 EST [-] Log opened.
+19:29 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+19:29 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+19:29 EST [-] Loading smtpclient-4.tac...
+19:29 EST [-] Loaded.
+19:29 EST [-] Starting factory &lt;twisted.internet.protocol.ClientFactory
+ instance at 0xb791e4ac&gt;
+19:29 EST [-] Enabling Multithreading.
+19:29 EST [-] Received SIGINT, shutting down.
+19:29 EST [Protocol,client] Stopping factory
+ &lt;twisted.internet.protocol.ClientFactory instance at
+ 0xb791e4ac&gt;
+19:29 EST [-] Main loop terminated.
+19:29 EST [-] Server Shut Down.
+exarkun@boson:~/doc/mail/tutorial/smtpclient$
+</pre>
+
+<p>But what does this
+mean? <code>twisted.internet.protocol.Protocol</code> is the
+base <em>protocol</em> implementation. For those familiar with the
+classic UNIX network services, it is equivalent to
+the <em>discard</em> service. It never produces any output and it
+discards all its input. Not terribly useful, and certainly nothing
+like an SMTP client. Let's see how we can improve this in the next
+example.</p>
+
+<h3>SMTP Client 5<a name="auto5"/></h3>
+
+<p>In <a href="smtpclient-5.tac" shape="rect">smtpclient-5.tac</a>, we will begin
+to use Twisted's SMTP protocol implementation for the first time.
+We'll make the obvious change, simply swapping
+out <code>twisted.internet.protocol.Protocol</code> in favor
+of <code>twisted.mail.smtp.ESMTPClient</code>. Don't worry about
+the <em>E</em> in <em>ESMTP</em>. It indicates we're actually using a
+newer version of the SMTP protocol. There is
+an <code>SMTPClient</code> in Twisted, but there's essentially no
+reason to ever use it.</p>
+
+<p>smtpclient-5.tac adds a new import:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">mail</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">smtp</span>
+</pre>
+
+<p>All of the mail related code in Twisted exists beneath
+the <code>twisted.mail</code> package. More specifically, everything
+having to do with the SMTP protocol implementation is defined in
+the <code>twisted.mail.smtp</code> module.</p>
+
+<p>Next we remove a line we added in smtpclient-4.tac:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientFactory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">Protocol</span>
+</pre>
+
+<p>And add a similar one in its place:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientFactory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">smtp</span>.<span class="py-src-variable">ESMTPClient</span>
+</pre>
+
+<p>Our client factory is now using a protocol implementation which
+behaves as an SMTP client. What happens when we try to run this
+version?</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-5.tac
+19:42 EST [-] Log opened.
+19:42 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+19:42 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+19:42 EST [-] Loading smtpclient-5.tac...
+19:42 EST [-] Loaded.
+19:42 EST [-] Starting factory &lt;twisted.internet.protocol.ClientFactory
+ instance at 0xb791e54c&gt;
+19:42 EST [-] Enabling Multithreading.
+19:42 EST [Uninitialized] Traceback (most recent call last):
+ File &quot;twisted/python/log.py&quot;, line 56, in callWithLogger
+ return callWithContext({&quot;system&quot;: lp}, func, *args, **kw)
+ File &quot;twisted/python/log.py&quot;, line 41, in callWithContext
+ return context.call({ILogContext: newCtx}, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 52, in callWithContext
+ return self.currentContext().callWithContext(ctx, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 31, in callWithContext
+ return func(*args,**kw)
+ --- &lt;exception caught here&gt; ---
+ File &quot;twisted/internet/selectreactor.py&quot;, line 139, in _doReadOrWrite
+ why = getattr(selectable, method)()
+ File &quot;twisted/internet/tcp.py&quot;, line 543, in doConnect
+ self._connectDone()
+ File &quot;twisted/internet/tcp.py&quot;, line 546, in _connectDone
+ self.protocol = self.connector.buildProtocol(self.getPeer())
+ File &quot;twisted/internet/base.py&quot;, line 641, in buildProtocol
+ return self.factory.buildProtocol(addr)
+ File &quot;twisted/internet/protocol.py&quot;, line 99, in buildProtocol
+ p = self.protocol()
+ exceptions.TypeError: __init__() takes at least 2 arguments (1 given)
+
+19:42 EST [Uninitialized] Stopping factory
+ &lt;twisted.internet.protocol.ClientFactory instance at
+ 0xb791e54c&gt;
+19:43 EST [-] Received SIGINT, shutting down.
+19:43 EST [-] Main loop terminated.
+19:43 EST [-] Server Shut Down.
+exarkun@boson:~/doc/mail/tutorial/smtpclient$
+</pre>
+
+
+<p>Oops, back to getting a traceback. This time, the default
+implementation of <code>buildProtocol</code> seems no longer to be
+sufficient. It instantiates the protocol with no arguments,
+but <code>ESMTPClient</code> wants at least one argument. In the next
+version of the client, we'll override <code>buildProtocol</code> to
+fix this problem.</p>
+
+<h3>SMTP Client 6<a name="auto6"/></h3>
+
+<p><a href="smtpclient-6.tac" shape="rect">smtpclient-6.tac</a> introduces
+a <code>twisted.internet.protocol.ClientFactory</code> subclass with
+an overridden <code>buildProtocol</code> method to overcome the
+problem encountered in the previous example.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SMTPClientFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">smtp</span>.<span class="py-src-variable">ESMTPClient</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">protocol</span>(<span class="py-src-variable">secret</span>=<span class="py-src-variable">None</span>, <span class="py-src-variable">identity</span>=<span class="py-src-string">'example.com'</span>)
+</pre>
+
+<p>The overridden method does almost the same thing as the base
+implementation: the only change is that it passes values for two
+arguments to <code>twisted.mail.smtp.ESMTPClient</code>'s initializer.
+The <code>secret</code> argument is used for SMTP authentication
+(which we will not attempt yet). The <code>identity</code> argument
+is used as a to identify ourselves Another minor change to note is
+that the <code>protocol</code> attribute is now defined in the class
+definition, rather than tacked onto an instance after one is created.
+This means it is a class attribute, rather than an instance attribute,
+now, which makes no difference as far as this example is concerned.
+There are circumstances in which the difference is important: be sure
+you understand the implications of each approach when creating your
+own factories.</p>
+
+<p>One other change is required: instead of
+instantiating <code>twisted.internet.protocol.ClientFactory</code>, we
+will now instantiate <code>SMTPClientFactory</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientFactory</span> = <span class="py-src-variable">SMTPClientFactory</span>()
+</pre>
+
+<p>Running this version of the code, we observe that the
+code <strong>still</strong> isn't quite traceback-free.</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-6.tac
+21:17 EST [-] Log opened.
+21:17 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+21:17 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+21:17 EST [-] Loading smtpclient-6.tac...
+21:17 EST [-] Loaded.
+21:17 EST [-] Starting factory &lt;__builtin__.SMTPClientFactory instance
+ at 0xb77fd68c&gt;
+21:17 EST [-] Enabling Multithreading.
+21:17 EST [ESMTPClient,client] Traceback (most recent call last):
+ File &quot;twisted/python/log.py&quot;, line 56, in callWithLogger
+ return callWithContext({&quot;system&quot;: lp}, func, *args, **kw)
+ File &quot;twisted/python/log.py&quot;, line 41, in callWithContext
+ return context.call({ILogContext: newCtx}, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 52, in callWithContext
+ return self.currentContext().callWithContext(ctx, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 31, in callWithContext
+ return func(*args,**kw)
+ --- &lt;exception caught here&gt; ---
+ File &quot;twisted/internet/selectreactor.py&quot;, line 139, in _doReadOrWrite
+ why = getattr(selectable, method)()
+ File &quot;twisted/internet/tcp.py&quot;, line 351, in doRead
+ return self.protocol.dataReceived(data)
+ File &quot;twisted/protocols/basic.py&quot;, line 221, in dataReceived
+ why = self.lineReceived(line)
+ File &quot;twisted/mail/smtp.py&quot;, line 1039, in lineReceived
+ why = self._okresponse(self.code,'\n'.join(self.resp))
+ File &quot;twisted/mail/smtp.py&quot;, line 1281, in esmtpState_serverConfig
+ self.tryTLS(code, resp, items)
+ File &quot;twisted/mail/smtp.py&quot;, line 1294, in tryTLS
+ self.authenticate(code, resp, items)
+ File &quot;twisted/mail/smtp.py&quot;, line 1343, in authenticate
+ self.smtpState_from(code, resp)
+ File &quot;twisted/mail/smtp.py&quot;, line 1062, in smtpState_from
+ self._from = self.getMailFrom()
+ File &quot;twisted/mail/smtp.py&quot;, line 1137, in getMailFrom
+ raise NotImplementedError
+ exceptions.NotImplementedError:
+
+21:17 EST [ESMTPClient,client] Stopping factory
+ &lt;__builtin__.SMTPClientFactory instance at 0xb77fd68c&gt;
+21:17 EST [-] Received SIGINT, shutting down.
+21:17 EST [-] Main loop terminated.
+21:17 EST [-] Server Shut Down.
+exarkun@boson:~/doc/mail/tutorial/smtpclient$
+</pre>
+
+<p>What we have accomplished with this iteration of the example is to
+navigate far enough into an SMTP transaction that Twisted is now
+interested in calling back to application-level code to determine what
+its next step should be. In the next example, we'll see how to
+provide that information to it.</p>
+
+<h3>SMTP Client 7<a name="auto7"/></h3>
+
+<p>SMTP Client 7 is the first version of our SMTP client which
+actually includes message data to transmit. For simplicity's sake,
+the message is defined as part of a new class. In a useful program
+which sent email, message data might be pulled in from the filesystem,
+a database, or be generated based on
+user-input. <a href="smtpclient-7.tac" shape="rect">smtpclient-7.tac</a>, however,
+defines a new class, <code>SMTPTutorialClient</code>, with three class
+attributes (<code>mailFrom</code>, <code>mailTo</code>,
+and <code>mailData</code>):</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SMTPTutorialClient</span>(<span class="py-src-parameter">smtp</span>.<span class="py-src-parameter">ESMTPClient</span>):
+ <span class="py-src-variable">mailFrom</span> = <span class="py-src-string">&quot;tutorial_sender@example.com&quot;</span>
+ <span class="py-src-variable">mailTo</span> = <span class="py-src-string">&quot;tutorial_recipient@example.net&quot;</span>
+ <span class="py-src-variable">mailData</span> = <span class="py-src-string">'''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy &lt;tutorial_sender@example.com&gt;
+To: Tutorial Gal &lt;tutorial_recipient@example.net&gt;
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''</span>
+</pre>
+
+<p>This statically defined data is accessed later in the class
+definition by three of the methods which are part of the
+ <em>SMTPClient callback API</em>. Twisted expects each of the three
+methods below to be defined and to return an object with a particular
+meaning. First, <code>getMailFrom</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailFrom</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">mailFrom</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">mailFrom</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">result</span>
+</pre>
+
+<p>This method is called to determine the <em>reverse-path</em>,
+otherwise known as the <em>envelope from</em>, of the message. This
+value will be used when sending the <code>MAIL FROM</code> SMTP
+command. The method must return a string which conforms to the <a href="http://www.faqs.org/rfcs/rfc2821.html" shape="rect">RFC 2821</a> definition
+of a <em>reverse-path</em>. In simpler terms, it should be a string
+like <code>&quot;alice@example.com&quot;</code>. Only one <em>envelope
+from</em> is allowed by the SMTP protocol, so it cannot be a list of
+strings or a comma separated list of addresses. Our implementation
+of <code>getMailFrom</code> does a little bit more than just return a
+string; we'll get back to this in a little bit.</p>
+
+<p>The next method is <code>getMailTo</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailTo</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> [<span class="py-src-variable">self</span>.<span class="py-src-variable">mailTo</span>]
+</pre>
+
+<p><code>getMailTo</code> is similar to <code>getMailFrom</code>. It
+returns one or more RFC 2821 addresses (this time a
+ <em>forward-path</em>, or <em>envelope to</em>). Since SMTP allows
+multiple recipients, <code>getMailTo</code> returns a list of these
+addresses. The list must contain at least one address, and even if
+there is exactly one recipient, it must still be in a list.</p>
+
+<p>The final callback we will define to provide information to
+Twisted is <code>getMailData</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailData</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">StringIO</span>.<span class="py-src-variable">StringIO</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">mailData</span>)
+</pre>
+
+<p>This one is quite simple as well: it returns a file or a file-like
+object which contains the message contents. In our case, we return
+a <code>StringIO</code> since we already have a string containing our
+message. If the contents of the file returned
+by <code>getMailData</code> span multiple lines (as email messages
+often do), the lines should be <code>\n</code> delimited (as they
+would be when opening a text file in the <code>&quot;rt&quot;</code> mode):
+necessary newline translation will be performed
+by <code>SMTPClient</code> automatically.</p>
+
+<p>There is one more new callback method defined in smtpclient-7.tac.
+This one isn't for providing information about the messages to
+Twisted, but for Twisted to provide information about the success or
+failure of the message transmission to the application:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">sentMail</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">code</span>, <span class="py-src-parameter">resp</span>, <span class="py-src-parameter">numOk</span>, <span class="py-src-parameter">addresses</span>, <span class="py-src-parameter">log</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Sent'</span>, <span class="py-src-variable">numOk</span>, <span class="py-src-string">'messages'</span>
+</pre>
+
+<p>Each of the arguments to <code>sentMail</code> provides some
+information about the success or failure of the message transmission
+transaction. <code>code</code> is the response code from the ultimate
+command. For successful transactions, it will be 250. For transient
+failures (those which should be retried), it will be between 400 and
+499, inclusive. For permanent failures (this which will never work,
+no matter how many times you retry them), it will be between 500 and
+599.</p>
+
+<h3>SMTP Client 8<a name="auto8"/></h3>
+
+<p>Thus far we have succeeded in creating a Twisted client application
+which starts up, connects to a (possibly) remote host, transmits some
+data, and disconnects. Notably missing, however, is application
+shutdown. Hitting ^C is fine during development, but it's not exactly
+a long-term solution. Fortunately, programmatic shutdown is extremely
+simple. <a href="smtpclient-8.tac" shape="rect">smtpclient-8.tac</a>
+extends <code>sentMail</code> with these two lines:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+</pre>
+
+<p>The <code>stop</code> method of the reactor causes the main event
+loop to exit, allowing a Twisted server to shut down. With this
+version of the example, we see that the program actually terminates
+after sending the message, without user-intervention:</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-8.tac
+19:52 EST [-] Log opened.
+19:52 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+19:52 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+19:52 EST [-] Loading smtpclient-8.tac...
+19:52 EST [-] Loaded.
+19:52 EST [-] Starting factory &lt;__builtin__.SMTPClientFactory instance
+ at 0xb791beec&gt;
+19:52 EST [-] Enabling Multithreading.
+19:52 EST [SMTPTutorialClient,client] Sent 1 messages
+19:52 EST [SMTPTutorialClient,client] Stopping factory
+ &lt;__builtin__.SMTPClientFactory instance at 0xb791beec&gt;
+19:52 EST [-] Main loop terminated.
+19:52 EST [-] Server Shut Down.
+exarkun@boson:~/doc/mail/tutorial/smtpclient$
+</pre>
+
+<h3>SMTP Client 9<a name="auto9"/></h3>
+
+<p>One task remains to be completed in this tutorial SMTP client:
+instead of always sending mail through a well-known host, we will look
+up the mail exchange server for the recipient address and try to
+deliver the message to that host.</p>
+
+<p>In <a href="smtpclient-9.tac" shape="rect">smtpclient-9.tac</a>, we'll take the
+first step towards this feature by defining a function which returns
+the mail exchange host for a particular domain:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailExchange</span>(<span class="py-src-parameter">host</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'localhost'</span>
+</pre>
+
+<p>Obviously this doesn't return the correct mail exchange host yet
+(in fact, it returns the exact same host we have been using all
+along), but pulling out the logic for determining which host to
+connect to into a function like this is the first step towards our
+ultimate goal. Now that we have <code>getMailExchange</code>, we'll
+call it when constructing our <code>TCPClient</code> service:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(
+ <span class="py-src-variable">getMailExchange</span>(<span class="py-src-string">'example.net'</span>), <span class="py-src-number">25</span>, <span class="py-src-variable">smtpClientFactory</span>)
+</pre>
+
+<p>We'll expand on the definition of <code>getMailExchange</code> in
+the next example.</p>
+
+<h3>SMTP Client 10<a name="auto10"/></h3>
+
+<p>In the previous example we defined <code>getMailExchange</code> to
+return a string representing the mail exchange host for a particular
+domain. While this was a step in the right direction, it turns out
+not to be a very big one. Determining the mail exchange host for a
+particular domain is going to involve network traffic (specifically,
+some DNS requests). These might take an arbitrarily large amount of
+time, so we need to introduce a <code>Deferred</code> to represent the
+result of <code>getMailExchange</code>. <a href="smtpclient-10.tac" shape="rect">smtpclient-10.tac</a> redefines it
+thusly:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailExchange</span>(<span class="py-src-parameter">host</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">'localhost'</span>)
+</pre>
+
+<p><code>defer.succeed</code> is a function which creates a
+new <code>Deferred</code> which already has a result, in this
+case <code>'localhost'</code>. Now we need to adjust
+our <code>TCPClient</code>-constructing code to expect and properly
+handle this <code>Deferred</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">cbMailExchange</span>(<span class="py-src-parameter">exchange</span>):
+ <span class="py-src-variable">smtpClientFactory</span> = <span class="py-src-variable">SMTPClientFactory</span>()
+
+ <span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">exchange</span>, <span class="py-src-number">25</span>, <span class="py-src-variable">smtpClientFactory</span>)
+ <span class="py-src-variable">smtpClientService</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+
+<span class="py-src-variable">getMailExchange</span>(<span class="py-src-string">'example.net'</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbMailExchange</span>)
+</pre>
+
+<p>An in-depth exploration of <code>Deferred</code>s is beyond the
+scope of this document. For such a look, see
+the <a href="../../../core/howto/defer.html" shape="rect">Deferred Reference</a>.
+However, in brief, what this version of the code does is to delay the
+creation of the <code>TCPClient</code> until the <code>Deferred</code>
+returned by <code>getMailExchange</code> fires. Once it does, we
+proceed normally through the creation of
+our <code>SMTPClientFactory</code> and <code>TCPClient</code>, as well
+as set the <code>TCPClient</code>'s service parent, just as we did in
+the previous examples.</p>
+
+<h3>SMTP Client 11<a name="auto11"/></h3>
+
+<p>At last we're ready to perform the mail exchange lookup. We do
+this by calling on an object provided specifically for this
+task, <code>twisted.mail.relaymanager.MXCalculator</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailExchange</span>(<span class="py-src-parameter">host</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">cbMX</span>(<span class="py-src-parameter">mxRecord</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">str</span>(<span class="py-src-variable">mxRecord</span>.<span class="py-src-variable">name</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">relaymanager</span>.<span class="py-src-variable">MXCalculator</span>().<span class="py-src-variable">getMX</span>(<span class="py-src-variable">host</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbMX</span>)
+</pre>
+
+<p>Because <code>getMX</code> returns a <code>Record_MX</code> object
+rather than a string, we do a little bit of post-processing to get the
+results we want. We have already converted the rest of the tutorial
+application to expect a <code>Deferred</code>
+from <code>getMailExchange</code>, so no further changes are
+required. <a href="smtpclient-11.tac" shape="rect">smtpclient-11.tac</a> completes
+this tutorial by being able to both look up the mail exchange host for
+the recipient domain, connect to it, complete an SMTP transaction,
+report its results, and finally shut down the reactor.</p>
+
+
+
+</div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/mail/tutorial/smtpserver/smtpserver-1.tac b/doc/mail/tutorial/smtpserver/smtpserver-1.tac
new file mode 100644
index 0000000..4804723
--- /dev/null
+++ b/doc/mail/tutorial/smtpserver/smtpserver-1.tac
@@ -0,0 +1,3 @@
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
diff --git a/doc/mail/tutorial/smtpserver/smtpserver-2.tac b/doc/mail/tutorial/smtpserver/smtpserver-2.tac
new file mode 100644
index 0000000..00f143a
--- /dev/null
+++ b/doc/mail/tutorial/smtpserver/smtpserver-2.tac
@@ -0,0 +1,10 @@
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpServerFactory = protocol.ServerFactory()
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpserver/smtpserver-3.tac b/doc/mail/tutorial/smtpserver/smtpserver-3.tac
new file mode 100644
index 0000000..5ff3cb5
--- /dev/null
+++ b/doc/mail/tutorial/smtpserver/smtpserver-3.tac
@@ -0,0 +1,12 @@
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpServerFactory = protocol.ServerFactory()
+smtpServerFactory.protocol = protocol.Protocol
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpserver/smtpserver-4.tac b/doc/mail/tutorial/smtpserver/smtpserver-4.tac
new file mode 100644
index 0000000..a8ee09e
--- /dev/null
+++ b/doc/mail/tutorial/smtpserver/smtpserver-4.tac
@@ -0,0 +1,14 @@
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpServerFactory = protocol.ServerFactory()
+
+from twisted.mail import smtp
+smtpServerFactory.protocol = smtp.ESMTP
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpserver/smtpserver-5.tac b/doc/mail/tutorial/smtpserver/smtpserver-5.tac
new file mode 100644
index 0000000..6f3a961
--- /dev/null
+++ b/doc/mail/tutorial/smtpserver/smtpserver-5.tac
@@ -0,0 +1,50 @@
+import os
+from zope.interface import implements
+
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol, defer
+
+smtpServerFactory = protocol.ServerFactory()
+
+from twisted.mail import smtp
+
+class FileMessage(object):
+ implements(smtp.IMessage)
+
+ def __init__(self, fileObj):
+ self.fileObj = fileObj
+
+ def lineReceived(self, line):
+ self.fileObj.write(line + '\n')
+
+ def eomReceived(self):
+ self.fileObj.close()
+ return defer.succeed(None)
+
+ def connectionLost(self):
+ self.fileObj.close()
+ os.remove(self.fileObj.name)
+
+class TutorialESMTP(smtp.ESMTP):
+ counter = 0
+
+ def validateTo(self, user):
+ fileName = 'tutorial-smtp.' + str(self.counter)
+ self.counter += 1
+ return lambda: FileMessage(file(fileName, 'w'))
+
+ def validateFrom(self, helo, origin):
+ return origin
+
+ def receivedHeader(self, helo, origin, recipients):
+ return 'Received: Tutorially.'
+
+
+smtpServerFactory.protocol = TutorialESMTP
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpserver/smtpserver-6.tac b/doc/mail/tutorial/smtpserver/smtpserver-6.tac
new file mode 100644
index 0000000..5924384
--- /dev/null
+++ b/doc/mail/tutorial/smtpserver/smtpserver-6.tac
@@ -0,0 +1,57 @@
+import os
+from zope.interface import implements
+
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol, defer
+
+smtpServerFactory = protocol.ServerFactory()
+
+from twisted.mail import smtp
+
+class FileMessage(object):
+ implements(smtp.IMessage)
+
+ def __init__(self, fileObj):
+ self.fileObj = fileObj
+
+ def lineReceived(self, line):
+ self.fileObj.write(line + '\n')
+
+ def eomReceived(self):
+ self.fileObj.close()
+ return defer.succeed(None)
+
+ def connectionLost(self):
+ self.fileObj.close()
+ os.remove(self.fileObj.name)
+
+class TutorialESMTP(smtp.ESMTP):
+ counter = 0
+
+ def validateTo(self, user):
+ fileName = 'tutorial-smtp.' + str(self.counter)
+ self.counter += 1
+ return lambda: FileMessage(file(fileName, 'w'))
+
+ def validateFrom(self, helo, origin):
+ return origin
+
+ def receivedHeader(self, helo, origin, recipients):
+ return 'Received: Tutorially.'
+
+class TutorialESMTPFactory(protocol.ServerFactory):
+ protocol = TutorialESMTP
+
+ def buildProtocol(self, addr):
+ p = self.protocol()
+ p.factory = self
+ return p
+
+smtpServerFactory.protocol = TutorialESMTP
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpserver/smtpserver-7.tac b/doc/mail/tutorial/smtpserver/smtpserver-7.tac
new file mode 100644
index 0000000..db98032
--- /dev/null
+++ b/doc/mail/tutorial/smtpserver/smtpserver-7.tac
@@ -0,0 +1,57 @@
+import os
+from zope.interface import implements
+
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol, defer
+
+from twisted.mail import smtp
+
+class FileMessage(object):
+ implements(smtp.IMessage)
+
+ def __init__(self, fileObj):
+ self.fileObj = fileObj
+
+ def lineReceived(self, line):
+ self.fileObj.write(line + '\n')
+
+ def eomReceived(self):
+ self.fileObj.close()
+ return defer.succeed(None)
+
+ def connectionLost(self):
+ self.fileObj.close()
+ os.remove(self.fileObj.name)
+
+class TutorialDelivery(object):
+ implements(smtp.IMessageDelivery)
+ counter = 0
+
+ def validateTo(self, user):
+ fileName = 'tutorial-smtp.' + str(self.counter)
+ self.counter += 1
+ return lambda: FileMessage(file(fileName, 'w'))
+
+ def validateFrom(self, helo, origin):
+ return origin
+
+ def receivedHeader(self, helo, origin, recipients):
+ return 'Received: Tutorially.'
+
+class TutorialESMTPFactory(protocol.ServerFactory):
+ protocol = smtp.ESMTP
+
+ def buildProtocol(self, addr):
+ p = self.protocol()
+ p.delivery = TutorialDelivery()
+ p.factory = self
+ return p
+
+smtpServerFactory = TutorialESMTPFactory()
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/doc/mail/tutorial/smtpserver/smtpserver-8.tac b/doc/mail/tutorial/smtpserver/smtpserver-8.tac
new file mode 100644
index 0000000..6133912
--- /dev/null
+++ b/doc/mail/tutorial/smtpserver/smtpserver-8.tac
@@ -0,0 +1,63 @@
+import os
+from zope.interface import implements
+
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol, defer
+
+from twisted.mail import smtp
+
+class FileMessage(object):
+ implements(smtp.IMessage)
+
+ def __init__(self, fileObj):
+ self.fileObj = fileObj
+
+ def lineReceived(self, line):
+ self.fileObj.write(line + '\n')
+
+ def eomReceived(self):
+ self.fileObj.close()
+ return defer.succeed(None)
+
+ def connectionLost(self):
+ self.fileObj.close()
+ os.remove(self.fileObj.name)
+
+class TutorialDelivery(object):
+ implements(smtp.IMessageDelivery)
+ counter = 0
+
+ def validateTo(self, user):
+ fileName = 'tutorial-smtp.' + str(self.counter)
+ self.counter += 1
+ return lambda: FileMessage(file(fileName, 'w'))
+
+ def validateFrom(self, helo, origin):
+ return origin
+
+ def receivedHeader(self, helo, origin, recipients):
+ return 'Received: Tutorially.'
+
+class TutorialDeliveryFactory(object):
+ implements(smtp.IMessageDeliveryFactory)
+
+ def getMessageDelivery(self):
+ return TutorialDelivery()
+
+class TutorialESMTPFactory(protocol.ServerFactory):
+ protocol = smtp.ESMTP
+
+ def buildProtocol(self, addr):
+ p = self.protocol()
+ p.deliveryFactory = TutorialDeliveryFactory()
+ p.factory = self
+ return p
+
+smtpServerFactory = TutorialESMTPFactory()
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/doc/names/examples/dns-service.py b/doc/names/examples/dns-service.py
new file mode 100755
index 0000000..396c91f
--- /dev/null
+++ b/doc/names/examples/dns-service.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Sample app to lookup SRV records in DNS.
+To run this script:
+$ python dns-service.py <service> <proto> <domain>
+where,
+service = the symbolic name of the desired service.
+proto = the transport protocol of the desired service; this is usually either TCP or UDP.
+domain = the domain name for which this record is valid.
+e.g.:
+$ python dns-service.py sip udp yahoo.com
+$ python dns-service.py xmpp-client tcp gmail.com
+"""
+
+from twisted.names import client
+from twisted.internet import reactor
+import sys
+
+def printAnswer((answers, auth, add)):
+ if not len(answers):
+ print 'No answers'
+ else:
+ print '\n'.join([str(x.payload) for x in answers])
+ reactor.stop()
+
+def printFailure(arg):
+ print "error: could not resolve:", arg
+ reactor.stop()
+
+try:
+ service, proto, domain = sys.argv[1:]
+except ValueError:
+ sys.stderr.write('%s: usage:\n' % sys.argv[0] +
+ ' %s SERVICE PROTO DOMAIN\n' % sys.argv[0])
+ sys.exit(1)
+
+resolver = client.Resolver('/etc/resolv.conf')
+d = resolver.lookupService('_%s._%s.%s' % (service, proto, domain), [1])
+d.addCallbacks(printAnswer, printFailure)
+
+reactor.run()
diff --git a/doc/names/examples/gethostbyname.py b/doc/names/examples/gethostbyname.py
new file mode 100755
index 0000000..e10fd43
--- /dev/null
+++ b/doc/names/examples/gethostbyname.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Returns the IP address for a given hostname.
+To run this script:
+$ python gethostbyname.py <hostname>
+e.g.:
+$ python gethostbyname.py www.google.com
+"""
+import sys
+from twisted.names import client
+from twisted.internet import reactor
+
+def gotResult(result):
+ print result
+ reactor.stop()
+
+def gotFailure(failure):
+ failure.printTraceback()
+ reactor.stop()
+
+d = client.getHostByName(sys.argv[1])
+d.addCallbacks(gotResult, gotFailure)
+
+reactor.run()
diff --git a/doc/names/examples/index.html b/doc/names/examples/index.html
new file mode 100644
index 0000000..892dd3e
--- /dev/null
+++ b/doc/names/examples/index.html
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Names code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Names code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">DNS (Twisted Names)</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>DNS (Twisted Names)<a name="auto0"/></h2>
+ <ul>
+ <li><a href="testdns.py" shape="rect">testdns.py</a> - Prints the results of an Address record lookup, Mail-Exchanger record lookup, and Nameserver record lookup for the given hostname for a given hostname.</li>
+ <li><a href="dns-service.py" shape="rect">dns-service.py</a> - Searches for SRV records in DNS.</li>
+ <li><a href="gethostbyname.py" shape="rect">gethostbyname.py</a> - Returns the IP address for a given hostname.</li>
+ </ul>
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/names/examples/testdns.py b/doc/names/examples/testdns.py
new file mode 100644
index 0000000..9fa3f03
--- /dev/null
+++ b/doc/names/examples/testdns.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Prints the results of an Address record lookup, Mail-Exchanger record lookup,
+and Nameserver record lookup for the given hostname for a given hostname.
+
+To run this script:
+$ python testdns.py <hostname>
+e.g.:
+$ python testdns.py www.google.com
+"""
+
+import sys
+from twisted.names import client
+from twisted.internet import reactor
+from twisted.names import dns
+
+r = client.Resolver('/etc/resolv.conf')
+
+def gotAddress(a):
+ print 'Addresses: ', ', '.join(map(str, a))
+
+def gotMails(a):
+ print 'Mail Exchangers: ', ', '.join(map(str, a))
+
+def gotNameservers(a):
+ print 'Nameservers: ', ', '.join(map(str, a))
+
+def gotError(f):
+ print 'gotError'
+ f.printTraceback()
+
+ from twisted.internet import reactor
+ reactor.stop()
+
+
+if __name__ == '__main__':
+ import sys
+
+ r.lookupAddress(sys.argv[1]).addCallback(gotAddress).addErrback(gotError)
+ r.lookupMailExchange(sys.argv[1]).addCallback(gotMails).addErrback(gotError)
+ r.lookupNameservers(sys.argv[1]).addCallback(gotNameservers).addErrback(gotError)
+
+ reactor.callLater(4, reactor.stop)
+ reactor.run()
diff --git a/doc/names/howto/index.html b/doc/names/howto/index.html
new file mode 100644
index 0000000..0448e99
--- /dev/null
+++ b/doc/names/howto/index.html
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Names Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Names Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+ <li><a href="names.html" shape="rect">Names DNS library</a></li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/names/howto/listings/names/example-domain.com b/doc/names/howto/listings/names/example-domain.com
new file mode 100644
index 0000000..e720019
--- /dev/null
+++ b/doc/names/howto/listings/names/example-domain.com
@@ -0,0 +1,37 @@
+
+zone = [
+ SOA(
+ # For whom we are the authority
+ 'example-domain.com',
+
+ # This nameserver's name
+ mname = "ns1.example-domain.com",
+
+ # Mailbox of individual who handles this
+ rname = "root.example-domain.com",
+
+ # Unique serial identifying this SOA data
+ serial = 2003010601,
+
+ # Time interval before zone should be refreshed
+ refresh = "1H",
+
+ # Interval before failed refresh should be retried
+ retry = "1H",
+
+ # Upper limit on time interval before expiry
+ expire = "1H",
+
+ # Minimum TTL
+ minimum = "1H"
+ ),
+
+ A('example-domain.com', '127.0.0.1'),
+ NS('example-domain.com', 'ns1.example-domain.com'),
+
+ CNAME('www.example-domain.com', 'example-domain.com'),
+ CNAME('ftp.example-domain.com', 'example-domain.com'),
+
+ MX('example-domain.com', 0, 'mail.example-domain.com'),
+ A('mail.example-domain.com', '123.0.16.43')
+]
diff --git a/doc/names/howto/names.html b/doc/names/howto/names.html
new file mode 100644
index 0000000..fb8b59c
--- /dev/null
+++ b/doc/names/howto/names.html
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Creating and working with a names (DNS) server</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Creating and working with a names (DNS) server</h1>
+ <div class="toc"><ol><li><a href="#auto0">Creating a non-authoritative server</a></li><li><a href="#auto1">Creating an authoritative server</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p>A Names server can be perform three basic operations:</p>
+
+<ul>
+<li>act as a recursive server, forwarding queries to other servers</li>
+<li>perform local caching of recursively discovered records</li>
+<li>act as the authoritative server for a domain</li>
+</ul>
+
+<h2>Creating a non-authoritative server<a name="auto0"/></h2>
+
+<p>
+The first two of these are easy, and you can create a server that performs them
+with the command <code class="shell">twistd -n dns --recursive --cache</code>.
+You may wish to run this as root since it will try to bind to UDP port 53. Try
+performing a lookup with it, <code class="shell">dig twistedmatrix.com
+@127.0.0.1</code>.
+</p>
+
+<h2>Creating an authoritative server<a name="auto1"/></h2>
+
+<p>To act as the authority for a domain, two things are necessary: the address
+of the machine on which the domain name server will run must be registered
+as a nameserver for the domain; and the domain name server must be
+configured to act as the authority. The first requirement is beyond the
+scope of this howto and will not be covered.
+</p>
+
+<p>To configure Names to act as the authority
+for <code>example-domain.com</code>, we first create a zone file for
+this domain.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+</p><span class="py-src-variable">zone</span> = [
+ <span class="py-src-variable">SOA</span>(
+ <span class="py-src-comment"># For whom we are the authority</span>
+ <span class="py-src-string">'example-domain.com'</span>,
+
+ <span class="py-src-comment"># This nameserver's name</span>
+ <span class="py-src-variable">mname</span> = <span class="py-src-string">&quot;ns1.example-domain.com&quot;</span>,
+
+ <span class="py-src-comment"># Mailbox of individual who handles this</span>
+ <span class="py-src-variable">rname</span> = <span class="py-src-string">&quot;root.example-domain.com&quot;</span>,
+
+ <span class="py-src-comment"># Unique serial identifying this SOA data</span>
+ <span class="py-src-variable">serial</span> = <span class="py-src-number">2003010601</span>,
+
+ <span class="py-src-comment"># Time interval before zone should be refreshed</span>
+ <span class="py-src-variable">refresh</span> = <span class="py-src-string">&quot;1H&quot;</span>,
+
+ <span class="py-src-comment"># Interval before failed refresh should be retried</span>
+ <span class="py-src-variable">retry</span> = <span class="py-src-string">&quot;1H&quot;</span>,
+
+ <span class="py-src-comment"># Upper limit on time interval before expiry</span>
+ <span class="py-src-variable">expire</span> = <span class="py-src-string">&quot;1H&quot;</span>,
+
+ <span class="py-src-comment"># Minimum TTL</span>
+ <span class="py-src-variable">minimum</span> = <span class="py-src-string">&quot;1H&quot;</span>
+ ),
+
+ <span class="py-src-variable">A</span>(<span class="py-src-string">'example-domain.com'</span>, <span class="py-src-string">'127.0.0.1'</span>),
+ <span class="py-src-variable">NS</span>(<span class="py-src-string">'example-domain.com'</span>, <span class="py-src-string">'ns1.example-domain.com'</span>),
+
+ <span class="py-src-variable">CNAME</span>(<span class="py-src-string">'www.example-domain.com'</span>, <span class="py-src-string">'example-domain.com'</span>),
+ <span class="py-src-variable">CNAME</span>(<span class="py-src-string">'ftp.example-domain.com'</span>, <span class="py-src-string">'example-domain.com'</span>),
+
+ <span class="py-src-variable">MX</span>(<span class="py-src-string">'example-domain.com'</span>, <span class="py-src-number">0</span>, <span class="py-src-string">'mail.example-domain.com'</span>),
+ <span class="py-src-variable">A</span>(<span class="py-src-string">'mail.example-domain.com'</span>, <span class="py-src-string">'123.0.16.43'</span>)
+]
+</pre><div class="caption">Zone file - <a href="listings/names/example-domain.com"><span class="filename">listings/names/example-domain.com</span></a></div></div>
+
+<p>Next, run the command <code class="shell">twistd -n dns --pyzone
+example-domain.com</code>. Now try querying the domain locally (again, with
+dig): <code class="shell">dig -t any example-domain.com @127.0.0.1</code>.
+</p>
+
+<p>Names can also read a traditional, BIND-syntax zone file. Specify these
+with the <code>--bindzone</code> parameter. The $GENERATE and $INCLUDE
+directives are not yet supported.
+</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/names/index.html b/doc/names/index.html
new file mode 100644
index 0000000..b4e545b
--- /dev/null
+++ b/doc/names/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Names Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Names Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Names to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Names</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/pair/examples/index.html b/doc/pair/examples/index.html
new file mode 100644
index 0000000..74b509e
--- /dev/null
+++ b/doc/pair/examples/index.html
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">Miscellaneous</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Miscellaneous<a name="auto0"/></h2>
+ <ul>
+ <li><a href="pairudp.py" shape="rect">pairudp.py</a> - UDP implemented with a TUN/TAP device</li>
+ </ul>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/pair/examples/pairudp.py b/doc/pair/examples/pairudp.py
new file mode 100644
index 0000000..b72155e
--- /dev/null
+++ b/doc/pair/examples/pairudp.py
@@ -0,0 +1,21 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import reactor, protocol
+from twisted.pair import ethernet, rawudp, ip
+from twisted.pair import tuntap
+
+class MyProto(protocol.DatagramProtocol):
+ def datagramReceived(self, *a, **kw):
+ print a, kw
+
+p_udp = rawudp.RawUDPProtocol()
+p_udp.addProto(42, MyProto())
+p_ip = ip.IPProtocol()
+p_ip.addProto(17, p_udp)
+p_eth = ethernet.EthernetProtocol()
+p_eth.addProto(0x800, p_ip)
+
+reactor.listenWith(tuntap.TuntapPort,
+ interface='tap0', proto=p_eth, reactor=reactor)
+reactor.run()
diff --git a/doc/pair/howto/index.html b/doc/pair/howto/index.html
new file mode 100644
index 0000000..77566bb
--- /dev/null
+++ b/doc/pair/howto/index.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Pair Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Pair Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+
+<li>Twisted Pair Documentation
+ <ul>
+ <li><a href="twisted-pair.html" shape="rect">Twisted Pair: Low-level networking</a></li>
+ </ul>
+</li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/pair/howto/twisted-pair.html b/doc/pair/howto/twisted-pair.html
new file mode 100644
index 0000000..535f22c
--- /dev/null
+++ b/doc/pair/howto/twisted-pair.html
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Pair: Low-level Networking</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Pair: Low-level Networking</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview of classes</a></li><ul><li><a href="#auto1">Transports</a></li><li><a href="#auto2">Protocols</a></li><li><a href="#auto3">Interfaces</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<p>Twisted can do low-level networking, too.</p>
+
+<p>Here's an example that tries to show the relationships of different
+classes and how data could flow for receiving packets.</p>
+
+<pre xml:space="preserve">
+FileWrapper
+ |
+ v
+PcapProtocol TuntapPort
+ | |
+ +------------+
+ v
+EthernetProtocol
+ |
+ +------------+-----------+---...
+ v v v
+IPProtocol ARPProtocol IPv6Protocol
+ |
+ +-------------+----------------+---...
+ v v v
+RawUDPProtocol RawICMPProtocol RawTCPProtocol
+ |
+ v
+DatagramProtocol
+</pre>
+
+<p>Of course, for writing, the picture would look pretty much
+identical, except all arrows would be reversed.</p>
+
+<h2>Overview of classes<a name="auto0"/></h2>
+
+<p>TODO</p>
+
+<h3>Transports<a name="auto1"/></h3>
+
+<p>TODO</p>
+
+<ul>
+<li>TuntapPort: TODO</li>
+</ul>
+
+<h3>Protocols<a name="auto2"/></h3>
+
+<p>TODO</p>
+
+<ul>
+<li>EthernetProtocol: TODO</li>
+<li>IPProtocol: TODO</li>
+<li>RawUDPProtocol: TODO</li>
+</ul>
+
+<h3>Interfaces<a name="auto3"/></h3>
+
+<p>TODO</p>
+
+<ul>
+<li>IRawDatagramProtocol: TODO</li>
+<li>IRawPacketProtocol: TODO</li>
+</ul>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/pair/index.html b/doc/pair/index.html
new file mode 100644
index 0000000..c1f3d04
--- /dev/null
+++ b/doc/pair/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Pair Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Pair Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+ <li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+ Twisted Pair to develop your own applications</li>
+ <li><a href="examples/index.html" shape="rect">Code Examples</a>: short code examples using
+ Twisted Pair</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/stylesheet.css b/doc/stylesheet.css
new file mode 100644
index 0000000..3c5961e
--- /dev/null
+++ b/doc/stylesheet.css
@@ -0,0 +1,189 @@
+
+body
+{
+ margin-left: 2em;
+ margin-right: 2em;
+ border: 0px;
+ padding: 0px;
+ font-family: sans-serif;
+ }
+
+.done { color: #005500; background-color: #99ff99 }
+.notdone { color: #550000; background-color: #ff9999;}
+
+pre
+{
+ padding: 1em;
+ border: thin black solid;
+ line-height: 1.2em;
+}
+
+.boxed
+{
+ padding: 1em;
+ border: thin black solid;
+}
+
+.shell
+{
+ background-color: #ffffdd;
+}
+
+.python
+{
+ background-color: #dddddd;
+}
+
+.htmlsource
+{
+ background-color: #dddddd;
+}
+
+.py-prototype
+{
+ background-color: #ddddff;
+}
+
+
+.python-interpreter
+{
+ background-color: #ddddff;
+}
+
+.doit
+{
+ border: thin blue dashed ;
+ background-color: #0ef
+}
+
+.py-src-comment
+{
+ color: #1111CC
+}
+
+.py-src-keyword
+{
+ color: #3333CC;
+ font-weight: bold;
+ line-height: 1.0em
+}
+
+.py-src-parameter
+{
+ color: #000066;
+ font-weight: bold;
+ line-height: 1.0em
+}
+
+.py-src-identifier
+{
+ color: #CC0000
+}
+
+.py-src-string
+{
+
+ color: #115511
+}
+
+.py-src-endmarker
+{
+ display: block; /* IE hack; prevents following line from being sucked into the py-listing box. */
+}
+
+.py-linenumber
+{
+ background-color: #cdcdcd;
+ float: left;
+ margin-top: 0px;
+ width: 4.0em
+}
+
+.py-listing, .html-listing, .listing
+{
+ margin: 1ex;
+ border: thin solid black;
+ background-color: #eee;
+}
+
+.py-listing pre, .html-listing pre, .listing pre
+{
+ margin: 0px;
+ border: none;
+ border-bottom: thin solid black;
+}
+
+.py-listing .python
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.html-listing .htmlsource
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.caption
+{
+ text-align: center;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+}
+
+.filename
+{
+ font-style: italic;
+ }
+
+.manhole-output
+{
+ color: blue;
+}
+
+hr
+{
+ display: inline;
+ }
+
+ul
+{
+ padding: 0px;
+ margin: 0px;
+ margin-left: 1em;
+ padding-left: 1em;
+ border-left: 1em;
+ }
+
+li
+{
+ padding: 2px;
+ }
+
+dt
+{
+ font-weight: bold;
+ margin-left: 1ex;
+ }
+
+dd
+{
+ margin-bottom: 1em;
+ }
+
+div.note
+{
+ background-color: #FFFFCC;
+ margin-top: 1ex;
+ margin-left: 5%;
+ margin-right: 5%;
+ padding-top: 1ex;
+ padding-left: 5%;
+ padding-right: 5%;
+ border: thin black solid;
+}
diff --git a/doc/web/examples/advogato.py b/doc/web/examples/advogato.py
new file mode 100644
index 0000000..5b60c6d
--- /dev/null
+++ b/doc/web/examples/advogato.py
@@ -0,0 +1,46 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This example demonstrates how to logon to a remote server and post a diary.
+
+Usage:
+ $ python advogato.py <name> <diary entry file>
+"""
+
+import sys
+from getpass import getpass
+
+from twisted.web.xmlrpc import Proxy
+from twisted.internet import reactor
+
+class AddDiary:
+
+ def __init__(self, name, password):
+ self.name = name
+ self.password = password
+ self.proxy = Proxy('http://advogato.org/XMLRPC')
+
+ def __call__(self, filename):
+ self.data = open(filename).read()
+ d = self.proxy.callRemote('authenticate', self.name, self.password)
+ d.addCallbacks(self.login, self.noLogin)
+
+ def noLogin(self, reason):
+ print "could not login"
+ reactor.stop()
+
+ def login(self, cookie):
+ d = self.proxy.callRemote('diary.set', cookie, -1, self.data)
+ d.addCallbacks(self.setDiary, self.errorSetDiary)
+
+ def setDiary(self, response):
+ reactor.stop()
+
+ def errorSetDiary(self, error):
+ print "could not set diary", error
+ reactor.stop()
+
+diary = AddDiary(sys.argv[1], getpass())
+diary(sys.argv[2])
+reactor.run()
diff --git a/doc/web/examples/dlpage.py b/doc/web/examples/dlpage.py
new file mode 100644
index 0000000..7e476f9
--- /dev/null
+++ b/doc/web/examples/dlpage.py
@@ -0,0 +1,23 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This example demonstrates how to use downloadPage.
+
+Usage:
+ $ python dlpage.py <url>
+
+Don't forget the http:// when you type a URL!
+"""
+
+from twisted.internet import reactor
+from twisted.web.client import downloadPage
+from twisted.python.util import println
+import sys
+
+# The function downloads a page and saves it to a file, in this case, it saves
+# the page to "foo".
+downloadPage(sys.argv[1], "foo").addCallbacks(
+ lambda value:reactor.stop(),
+ lambda error:(println("an error occurred",error),reactor.stop()))
+reactor.run()
diff --git a/doc/web/examples/fortune.rpy.py b/doc/web/examples/fortune.rpy.py
new file mode 100644
index 0000000..3520744
--- /dev/null
+++ b/doc/web/examples/fortune.rpy.py
@@ -0,0 +1,49 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This example demostrates how to render the output of a system process to a
+twisted web server.
+
+In order to run this, you need to have fortune installed. Fortune is a simple
+game that displays a random message from a database of quotations. You will need
+to change the path of the fortune program if it's not in the "/usr/game"
+directory.
+
+To test the script, rename the file to fortune.rpy, and move it to any
+directory, let's say /var/www/html/
+
+Now, start your Twisted web server:
+ $ twistd -n web --path /var/www/html/
+
+And visit http://127.0.0.1:8080/fortune.rpy with a web browser.
+"""
+
+from twisted.web.resource import Resource
+from twisted.web import server
+from twisted.internet import utils
+from twisted.python import util
+
+class FortuneResource(Resource):
+ """
+ This resource will only repond to HEAD & GET requests.
+ """
+ # Link your fortune program to /usr/games or change the path.
+ fortune = "/usr/games/fortune"
+
+ def render_GET(self, request):
+ """
+ Get a fortune and serve it as the response to this request.
+
+ Use L{utils.getProcessOutput}, which spawns a process and returns a
+ Deferred which fires with its output.
+ """
+ request.write("<pre>\n")
+ deferred = utils.getProcessOutput(self.fortune)
+ deferred.addCallback(lambda s:
+ (request.write(s+"\n"), request.finish()))
+ deferred.addErrback(lambda s:
+ (request.write(str(s)), request.finish()))
+ return server.NOT_DONE_YET
+
+resource = FortuneResource()
diff --git a/doc/web/examples/getpage.py b/doc/web/examples/getpage.py
new file mode 100644
index 0000000..31a2a2c
--- /dev/null
+++ b/doc/web/examples/getpage.py
@@ -0,0 +1,20 @@
+# Copyright (c) Twisted Matrix Laboratories
+# See LICENSE for details.
+
+"""
+This program will retrieve and print the resource at the given URL.
+
+Usage:
+ $ python getpage.py <URL>
+"""
+
+import sys
+
+from twisted.internet import reactor
+from twisted.web.client import getPage
+from twisted.python.util import println
+
+getPage(sys.argv[1]).addCallbacks(
+ callback=lambda value:(println(value),reactor.stop()),
+ errback=lambda error:(println("an error occurred", error),reactor.stop()))
+reactor.run()
diff --git a/doc/web/examples/google.py b/doc/web/examples/google.py
new file mode 100644
index 0000000..22dc9c2
--- /dev/null
+++ b/doc/web/examples/google.py
@@ -0,0 +1,21 @@
+# Copyright (c) Twisted Matrix Laboratories
+# See LICENSE for details.
+
+"""
+This program will print out the URL corresponding to the first webpage given by
+a Google search.
+
+Usage:
+ $ python google.py <keyword(s)>
+"""
+
+import sys
+
+from twisted.web.google import checkGoogle
+from twisted.python.util import println
+from twisted.internet import reactor
+
+checkGoogle(sys.argv[1:]).addCallbacks(
+ lambda l:(println(l),reactor.stop()),
+ lambda e:(println('error:',e),reactor.stop()))
+reactor.run()
diff --git a/doc/web/examples/hello.rpy.py b/doc/web/examples/hello.rpy.py
new file mode 100644
index 0000000..1480dd4
--- /dev/null
+++ b/doc/web/examples/hello.rpy.py
@@ -0,0 +1,38 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This is a resource file that renders a static web page.
+
+To test the script, rename the file to hello.rpy, and move it to any directory,
+let's say /var/www/html/.
+
+Now, start your Twisted web server:
+ $ twistd -n web --path /var/www/html/
+
+And visit http://127.0.0.1:8080/hello.rpy with a web browser.
+"""
+
+from twisted.web import static
+import time
+
+now = time.ctime()
+
+d = '''\
+<HTML><HEAD><TITLE>Hello Rpy</TITLE>
+
+<H1>Hello World, It is Now %(now)s</H1>
+
+<UL>
+''' % vars()
+
+for i in range(10):
+ d += "<LI>%(i)s" % vars()
+
+d += '''\
+</UL>
+
+</BODY></HTML>
+'''
+
+resource = static.Data(d, 'text/html')
diff --git a/doc/web/examples/httpclient.py b/doc/web/examples/httpclient.py
new file mode 100644
index 0000000..7d297f3
--- /dev/null
+++ b/doc/web/examples/httpclient.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This example demonstrates how to make a simple http client.
+
+Usage:
+ httpclient.py <url>
+
+Don't forget the http:// when you type the web address!
+"""
+
+import sys
+from pprint import pprint
+
+from twisted import version
+from twisted.python import log
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
+from twisted.internet.protocol import Protocol
+from twisted.web.iweb import UNKNOWN_LENGTH
+from twisted.web.http_headers import Headers
+from twisted.web.client import Agent, ResponseDone
+
+
+class WriteToStdout(Protocol):
+ def connectionMade(self):
+ self.onConnLost = Deferred()
+
+ def dataReceived(self, data):
+ """
+ Print out the html page received.
+ """
+ print 'Got some:', data
+
+ def connectionLost(self, reason):
+ if not reason.check(ResponseDone):
+ reason.printTraceback()
+ else:
+ print 'Response done'
+ self.onConnLost.callback(None)
+
+
+def main(reactor, url):
+ """
+ We create a custom UserAgent and send a GET request to a web server.
+ """
+ userAgent = 'Twisted/%s (httpclient.py)' % (version.short(),)
+ agent = Agent(reactor)
+ d = agent.request(
+ 'GET', url, Headers({'user-agent': [userAgent]}))
+ def cbResponse(response):
+ """
+ Prints out the response returned by the web server.
+ """
+ pprint(vars(response))
+ proto = WriteToStdout()
+ if response.length is not UNKNOWN_LENGTH:
+ print 'The response body will consist of', response.length, 'bytes.'
+ else:
+ print 'The response body length is unknown.'
+ response.deliverBody(proto)
+ return proto.onConnLost
+ d.addCallback(cbResponse)
+ d.addErrback(log.err)
+ d.addBoth(lambda ign: reactor.callWhenRunning(reactor.stop))
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main(reactor, *sys.argv[1:])
diff --git a/doc/web/examples/index.html b/doc/web/examples/index.html
new file mode 100644
index 0000000..429ac9f
--- /dev/null
+++ b/doc/web/examples/index.html
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Web code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Web code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">twisted.web.client</a></li><li><a href="#auto1">XML-RPC</a></li><li><a href="#auto2">Virtual hosts and proxies</a></li><li><a href="#auto3">.rpys and ResourceTemplate</a></li><li><a href="#auto4">Miscellaneous</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>twisted.web.client<a name="auto0"/></h2>
+ <ul>
+ <li><a href="getpage.py" shape="rect">getpage.py</a> - use
+ <code>twisted.web.client.getPage</code> to download a web
+ page.</li>
+ <li><a href="dlpage.py" shape="rect">dlpage.py</a> - add callbacks to
+ <code>twisted.web.client.downloadPage</code> to display errors
+ that occur when downloading a web page</li>
+ </ul>
+
+ <h2>XML-RPC<a name="auto1"/></h2>
+ <ul>
+ <li><a href="xmlrpc.py" shape="rect">xmlrpc.py</a> XML-RPC server with
+ several methods, including echoing, faulting, returning
+ deferreds and failed deferreds</li>
+ <li><a href="xmlrpcclient.py" shape="rect">xmlrpcclient.py</a> - use
+ <code>twisted.web.xmlrpc.Proxy</code> to call remote XML-RPC
+ methods</li>
+ <li><a href="advogato.py" shape="rect">advogato.py</a> - use
+ <code>twisted.web.xmlrpc</code> to post a diary entry to
+ advogato.org; requires an advogato account</li>
+ </ul>
+
+ <h2>Virtual hosts and proxies<a name="auto2"/></h2>
+ <ul>
+ <li><a href="proxy.py" shape="rect">proxy.py</a> -
+ use <code>twisted.web.proxy.Proxy</code> to make the simplest
+ proxy</li>
+ <li><a href="logging-proxy.py" shape="rect">logging-proxy.py</a> - example of
+ subclassing the core classes of <code>twisted.web.proxy</code>
+ to log requests through a proxy</li>
+ <li><a href="reverse-proxy.py" shape="rect">reverse-proxy.py</a> - use
+ <code>twisted.web.proxy.ReverseProxyResource</code> to make
+ any HTTP request to the proxy port get applied to a specified
+ website</li>
+ <li><a href="rootscript.py" shape="rect">rootscript.py</a> - example use of
+ <code>twisted.web.vhost.NameVirtualHost</code></li>
+ <li><a href="web.py" shape="rect">web.py</a> - an example of both using the
+ <code>processors</code> attribute to set how certain file types
+ are treated and using
+ <code>twisted.web.vhost.VHostMonsterResource</code> to reverse
+ proxy</li>
+ </ul>
+
+ <h2>.rpys and ResourceTemplate<a name="auto3"/></h2>
+ <ul>
+ <li><a href="hello.rpy.py" shape="rect">hello.rpy.py</a> - use
+ <code>twisted.web.static</code> to create a static resource to
+ serve</li>
+ <li><a href="fortune.rpy.py" shape="rect">fortune.rpy.py</a> - create a
+ resource that returns the output of a process run on the
+ server</li>
+ <li><a href="lj.rpy.py" shape="rect">lj.rpy.py</a> - use
+ <code>twisted.web.microdom</code>,
+ <code>twisted.web.domhelpers</code>, and chained callbacks to
+ extract and display parts of a livejournal user's rss page</li>
+ <li><a href="report.rpy.py" shape="rect">report.rpy.py</a> - display
+ various properties of a resource, including path, host, and
+ port</li>
+ <li><a href="users.rpy.py" shape="rect">users.rpy.py</a> - use
+ <code>twisted.web.distrib</code> to publish user directories
+ as for a &quot;community web site&quot;</li>
+ <li><a href="simple.rtl" shape="rect">simple.rtl</a> - example use of
+ <code>twisted.web.resource.ResourceTemplate</code></li>
+ </ul>
+
+ <h2>Miscellaneous<a name="auto4"/></h2>
+ <ul>
+ <li><a href="webguard.py" shape="rect">webguard.py</a> - pairing
+ <code>twisted.web</code> with <code>twisted.cred</code> to
+ guard resources against unauthenticated users</li>
+ <li><a href="silly-web.py" shape="rect">silly-web.py</a> - bare-bones
+ distributed web setup with a master and slave using
+ <code>twisted.web.distrib</code> and
+ <code>twisted.spread.pb</code></li>
+ <li><a href="google.py" shape="rect">google.py</a> - use
+ <code>twisted.web.google</code> to get the I'm Feeling Lucky
+ page for a search term</li>
+ <li><a href="soap.py" shape="rect">soap.py</a> - use
+ <code>twisted.web.soap</code> to publish SOAP methods</li>
+ </ul>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/examples/lj.rpy.py b/doc/web/examples/lj.rpy.py
new file mode 100644
index 0000000..6bc078d
--- /dev/null
+++ b/doc/web/examples/lj.rpy.py
@@ -0,0 +1,48 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+The example gets RSS feeds from LiveJournal users. It demonstrates how to use
+chained Deferred callbacks.
+
+To test the script, rename the file to lj.rpy, and move it to any directory,
+let's say /var/www/html/.
+
+Now, start your Twisted web server:
+ $ twistd -n web --path /var/www/html/
+
+And visit a URL like http://127.0.0.1:8080/lj.rpy?user=foo with a web browser,
+replacing "foo" with a valid LiveJournal username.
+"""
+
+from twisted.web import resource as resourcelib
+from twisted.web import client, microdom, domhelpers, server
+
+urlTemplate = 'http://%s.livejournal.com/data/rss'
+
+class LJSyndicatingResource(resourcelib.Resource):
+
+ def render_GET(self, request):
+ """
+ Get an xml feed from LiveJournal and construct a new HTML page using the
+ 'title' and 'link' parsed from the xml document.
+ """
+ url = urlTemplate % request.args['user'][0]
+ client.getPage(url, timeout=30).addCallback(
+ microdom.parseString).addCallback(
+ lambda t: domhelpers.findNodesNamed(t, 'item')).addCallback(
+ lambda itms: zip([domhelpers.findNodesNamed(x, 'title')[0]
+ for x in itms],
+ [domhelpers.findNodesNamed(x, 'link')[0]
+ for x in itms]
+ )).addCallback(
+ lambda itms: '<html><head></head><body><ul>%s</ul></body></html>' %
+ '\n'.join(
+ ['<li><a href="%s">%s</a></li>' % (
+ domhelpers.getNodeText(link), domhelpers.getNodeText(title))
+ for (title, link) in itms])
+ ).addCallback(lambda s: (request.write(s),request.finish())).addErrback(
+ lambda e: (request.write('Error: %s' % e),request.finish()))
+ return server.NOT_DONE_YET
+
+resource = LJSyndicatingResource()
diff --git a/doc/web/examples/logging-proxy.py b/doc/web/examples/logging-proxy.py
new file mode 100644
index 0000000..b75b1ab
--- /dev/null
+++ b/doc/web/examples/logging-proxy.py
@@ -0,0 +1,45 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An example of a proxy which logs all requests processed through it.
+
+Usage:
+ $ python logging-proxy.py
+
+Then configure your web browser to use localhost:8080 as a proxy, and visit a
+URL (This is not a SOCKS proxy). When browsing in this configuration, this
+example will proxy connections from the browser to the server indicated by URLs
+which are visited. The client IP and the request hostname will be logged for
+each request.
+
+HTTP is supported. HTTPS is not supported.
+
+See also proxy.py for a simpler proxy example.
+"""
+
+from twisted.internet import reactor
+from twisted.web import proxy, http
+
+class LoggingProxyRequest(proxy.ProxyRequest):
+ def process(self):
+ """
+ It's normal to see a blank HTTPS page. As the proxy only works
+ with the HTTP protocol.
+ """
+ print "Request from %s for %s" % (
+ self.getClientIP(), self.getAllHeaders()['host'])
+ try:
+ proxy.ProxyRequest.process(self)
+ except KeyError:
+ print "HTTPS is not supported at the moment!"
+
+class LoggingProxy(proxy.Proxy):
+ requestFactory = LoggingProxyRequest
+
+class LoggingProxyFactory(http.HTTPFactory):
+ def buildProtocol(self, addr):
+ return LoggingProxy()
+
+reactor.listenTCP(8080, LoggingProxyFactory())
+reactor.run()
diff --git a/doc/web/examples/proxy.py b/doc/web/examples/proxy.py
new file mode 100644
index 0000000..6a1ae8a
--- /dev/null
+++ b/doc/web/examples/proxy.py
@@ -0,0 +1,26 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This example demonstrates a very simple HTTP proxy.
+
+Usage:
+ $ python proxy.py
+
+Then configure your web browser to use localhost:8080 as a proxy, and visit a
+URL (This is not a SOCKS proxy). When browsing in this configuration, this
+example will proxy connections from the browser to the server indicated by URLs
+which are visited.
+
+See also logging-proxy.py for a proxy with additional features.
+"""
+
+from twisted.web import proxy, http
+from twisted.internet import reactor
+
+class ProxyFactory(http.HTTPFactory):
+ def buildProtocol(self, addr):
+ return proxy.Proxy()
+
+reactor.listenTCP(8080, ProxyFactory())
+reactor.run()
diff --git a/doc/web/examples/report.rpy.py b/doc/web/examples/report.rpy.py
new file mode 100644
index 0000000..a90a544
--- /dev/null
+++ b/doc/web/examples/report.rpy.py
@@ -0,0 +1,44 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This example demostrates how to get host information from a request object.
+
+To test the script, rename the file to report.rpy, and move it to any directory,
+let's say /var/www/html/.
+
+Now, start your Twist web server:
+ $ twistd -n web --path /var/www/html/
+
+Then visit http://127.0.0.1:8080/report.rpy in your web browser.
+"""
+
+from twisted.web.resource import Resource
+
+
+class ReportResource(Resource):
+
+ def render_GET(self, request):
+ path = request.path
+ host = request.getHost().host
+ port = request.getHost().port
+ url = request.prePathURL()
+ uri = request.uri
+ secure = (request.isSecure() and "securely") or "insecurely"
+ return ("""\
+<HTML>
+ <HEAD><TITLE>Welcome To Twisted Python Reporting</title></head>
+
+ <BODY><H1>Welcome To Twisted Python Reporting</H1>
+ <UL>
+ <LI>The path to me is %(path)s
+ <LI>The host I'm on is %(host)s
+ <LI>The port I'm on is %(port)s
+ <LI>I was accessed %(secure)s
+ <LI>A URL to me is %(url)s
+ <LI>My URI to me is %(uri)s
+ </UL>
+ </body>
+</html>""" % vars())
+
+resource = ReportResource()
diff --git a/doc/web/examples/reverse-proxy.py b/doc/web/examples/reverse-proxy.py
new file mode 100644
index 0000000..4ea57d7
--- /dev/null
+++ b/doc/web/examples/reverse-proxy.py
@@ -0,0 +1,18 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This example demonstrates how to run a reverse proxy.
+
+Run this example with:
+ $ python reverse-proxy.py
+
+Then visit http://localhost:8080/ in your web browser.
+"""
+
+from twisted.internet import reactor
+from twisted.web import proxy, server
+
+site = server.Site(proxy.ReverseProxyResource('www.yahoo.com', 80, ''))
+reactor.listenTCP(8080, site)
+reactor.run()
diff --git a/doc/web/examples/rootscript.py b/doc/web/examples/rootscript.py
new file mode 100644
index 0000000..7b228aa
--- /dev/null
+++ b/doc/web/examples/rootscript.py
@@ -0,0 +1,41 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This is a Twisted Web Server with Named-Based Virtual Host Support.
+
+Usage:
+ $ sudo twistd -ny rootscript.py
+
+Note: You need to edit your hosts file for this example
+to work. Need to add the following entry:
+
+ 127.0.0.1 example.com
+
+Then visit http://example.com/ with a web browser and compare the results to
+visiting http://localhost/.
+"""
+
+from twisted.web import vhost, static, script, server
+from twisted.application import internet, service
+
+default = static.Data('text/html', '')
+# Setting up vhost resource.
+default.putChild('vhost', vhost.VHostMonsterResource())
+resource = vhost.NameVirtualHost()
+resource.default = default
+# Here we use /var/www/html/ as our root diretory for the web server, you can
+# change it to whatever directory you want.
+root = static.File("/var/www/html/")
+root.processors = {'.rpy': script.ResourceScript}
+# addHost binds domain name example.com to our root resource.
+resource.addHost("example.com", root)
+
+# Setup Twisted Application.
+site = server.Site(resource)
+application = service.Application('vhost')
+sc = service.IServiceCollection(application)
+# Only the processes owned by the root user can listen @ port 80, change the
+# port number here if you don't want to run it as root.
+i = internet.TCPServer(80, site)
+i.setServiceParent(sc)
diff --git a/doc/web/examples/silly-web.py b/doc/web/examples/silly-web.py
new file mode 100644
index 0000000..758a532
--- /dev/null
+++ b/doc/web/examples/silly-web.py
@@ -0,0 +1,28 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This shows an example of a bare-bones distributed web set up. The "master" and
+"slave" parts will usually be in different files -- they are here together only
+for brevity of illustration. In normal usage they would each run in a separate
+process.
+
+Usage:
+ $ python silly-web.py
+
+Then visit http://localhost:19988/.
+"""
+
+from twisted.internet import reactor, protocol
+from twisted.web import server, distrib, static
+from twisted.spread import pb
+
+# The "master" server
+site = server.Site(distrib.ResourceSubscription('unix', '.rp'))
+reactor.listenTCP(19988, site)
+
+# The "slave" server
+fact = pb.PBServerFactory(distrib.ResourcePublisher(server.Site(static.File('static'))))
+
+reactor.listenUNIX('./.rp', fact)
+reactor.run()
diff --git a/doc/web/examples/simple.rtl b/doc/web/examples/simple.rtl
new file mode 100644
index 0000000..fbc08ff
--- /dev/null
+++ b/doc/web/examples/simple.rtl
@@ -0,0 +1,32 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This example demostrates how to render a page using a third-party template
+system.
+
+Usage:
+ $ twistd -n web --process=.rtl=twisted.web.script.ResourceTemplate --path /path/to/examples/
+
+And make sure Quixote is installed.
+"""
+
+from twisted.web.resource import Resource
+
+
+class ExampleResource(Resource):
+
+ def render_GET(self, request):
+ """\
+<HTML>
+ <HEAD><TITLE> Welcome To Twisted Python </title></head>
+
+ <BODY><ul>"""
+ for i in range(10):
+ '<LI>';i
+ """</ul></body>
+</html>"""
+
+
+resource = ExampleResource()
+
diff --git a/doc/web/examples/soap.py b/doc/web/examples/soap.py
new file mode 100644
index 0000000..e4edec1
--- /dev/null
+++ b/doc/web/examples/soap.py
@@ -0,0 +1,44 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This is an example of a simple SOAP server.
+
+Usage:
+ $ python soap.py
+
+An example session (assuming the server is running):
+
+ >>> import SOAPpy
+ >>> p = SOAPpy.SOAPProxy('http://localhost:8080/')
+ >>> p.add(a=1)
+ 1
+ >>> p.add(a=1, b=3)
+ 4
+ >>> p.echo("Hello World")
+ 'Hello World'
+
+"""
+
+from twisted.web import soap, server
+from twisted.internet import reactor, defer
+
+
+class Example(soap.SOAPPublisher):
+ """
+ It publishs two methods, 'add' and 'echo'.
+ """
+
+ def soap_echo(self, x):
+ return x
+
+ def soap_add(self, a=0, b=0):
+ return a + b
+ soap_add.useKeywords = 1
+
+ def soap_deferred(self):
+ return defer.succeed(2)
+
+
+reactor.listenTCP(8080, server.Site(Example()))
+reactor.run()
diff --git a/doc/web/examples/users.rpy.py b/doc/web/examples/users.rpy.py
new file mode 100644
index 0000000..046fcd1
--- /dev/null
+++ b/doc/web/examples/users.rpy.py
@@ -0,0 +1,24 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An example showing how to use a distributed web server's user directory support.
+
+With this, you can have an instant "community web site",
+letting your shell users publish data in secure ways.
+
+Just put this script anywhere, and /path/to/this/script/<user>/ will publish a
+user's ~/public_html, and a .../<user>.twistd/ will attempt to contact a user's
+personal web server.
+
+For example, if you put this at /var/www/users.rpy and run a server like:
+ $ twistd -n web --allow-ignore-ext --path /var/www
+
+Then http://example.com/users/<name>/ and http://example.com/users/<name>.twistd
+will work similarily to how they work on twistedmatrix.com.
+"""
+
+from twisted.web import distrib
+
+resource = distrib.UserDirectory()
+registry.setComponent(distrib.UserDirectory, resource)
diff --git a/doc/web/examples/web.py b/doc/web/examples/web.py
new file mode 100644
index 0000000..1eaccf0
--- /dev/null
+++ b/doc/web/examples/web.py
@@ -0,0 +1,29 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This demonstrates a web server which can run behind a name-based virtual hosting
+reverse proxy. It decodes modified URLs like:
+
+ host:port/vhost/http/external-host:port/
+
+and dispatches the request as if it had been received on the given protocol,
+external host, and port.
+
+Usage:
+ python web.py
+"""
+
+from twisted.internet import reactor
+from twisted.web import static, server, vhost, twcgi, script
+
+root = static.File("static")
+root.processors = {
+ '.cgi': twcgi.CGIScript,
+ '.epy': script.PythonScript,
+ '.rpy': script.ResourceScript,
+}
+root.putChild('vhost', vhost.VHostMonsterResource())
+site = server.Site(root)
+reactor.listenTCP(1999, site)
+reactor.run()
diff --git a/doc/web/examples/webguard.py b/doc/web/examples/webguard.py
new file mode 100644
index 0000000..02b0557
--- /dev/null
+++ b/doc/web/examples/webguard.py
@@ -0,0 +1,64 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This example shows how to make simple web authentication.
+
+To run the example:
+ $ python webguard.py
+
+When you visit http://127.0.0.1:8889/, the page will ask for an username &
+password. See the code in main() to get the correct username & password!
+"""
+
+import sys
+
+from zope.interface import implements
+
+from twisted.python import log
+from twisted.internet import reactor
+from twisted.web import server, resource, guard
+from twisted.cred.portal import IRealm, Portal
+from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+
+
+class GuardedResource(resource.Resource):
+ """
+ A resource which is protected by guard and requires authentication in order
+ to access.
+ """
+ def getChild(self, path, request):
+ return self
+
+
+ def render(self, request):
+ return "Authorized!"
+
+
+
+class SimpleRealm(object):
+ """
+ A realm which gives out L{GuardedResource} instances for authenticated
+ users.
+ """
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if resource.IResource in interfaces:
+ return resource.IResource, GuardedResource(), lambda: None
+ raise NotImplementedError()
+
+
+
+def main():
+ log.startLogging(sys.stdout)
+ checkers = [InMemoryUsernamePasswordDatabaseDontUse(joe='blow')]
+ wrapper = guard.HTTPAuthSessionWrapper(
+ Portal(SimpleRealm(), checkers),
+ [guard.DigestCredentialFactory('md5', 'example.com')])
+ reactor.listenTCP(8889, server.Site(
+ resource = wrapper))
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/web/examples/xmlrpc.py b/doc/web/examples/xmlrpc.py
new file mode 100644
index 0000000..9fde54c
--- /dev/null
+++ b/doc/web/examples/xmlrpc.py
@@ -0,0 +1,80 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An example of an XML-RPC server in Twisted.
+
+Usage:
+ $ python xmlrpc.py
+
+An example session (assuming the server is running):
+
+ >>> import xmlrpclib
+ >>> s = xmlrpclib.Server('http://localhost:7080/')
+ >>> s.echo("lala")
+ ['lala']
+ >>> s.echo("lala", 1)
+ ['lala', 1]
+ >>> s.echo("lala", 4)
+ ['lala', 4]
+ >>> s.echo("lala", 4, 3.4)
+ ['lala', 4, 3.3999999999999999]
+ >>> s.echo("lala", 4, [1, 2])
+ ['lala', 4, [1, 2]]
+
+"""
+
+from twisted.web import xmlrpc
+from twisted.internet import defer
+import xmlrpclib
+
+
+class Echoer(xmlrpc.XMLRPC):
+ """
+ An example object to be published.
+
+ Has five methods accessable by XML-RPC, 'echo', 'hello', 'defer',
+ 'defer_fail' and 'fail.
+ """
+
+ def xmlrpc_echo(self, *args):
+ """
+ Return all passed args.
+ """
+ return args
+
+ def xmlrpc_hello(self):
+ """
+ Return 'hello, world'.
+ """
+ return 'hello, world!'
+
+ def xmlrpc_defer(self):
+ """
+ Show how xmlrpc methods can return Deferred.
+ """
+ return defer.succeed("hello")
+
+ def xmlrpc_defer_fail(self):
+ """
+ Show how xmlrpc methods can return failed Deferred.
+ """
+ return defer.fail(12)
+
+ def xmlrpc_fail(self):
+ """
+ Show how we can return a failure code.
+ """
+ return xmlrpclib.Fault(7, "Out of cheese.")
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.web import server
+ r = Echoer()
+ reactor.listenTCP(7080, server.Site(r))
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/web/examples/xmlrpcclient.py b/doc/web/examples/xmlrpcclient.py
new file mode 100644
index 0000000..74c6eb0
--- /dev/null
+++ b/doc/web/examples/xmlrpcclient.py
@@ -0,0 +1,31 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This example makes remote XML-RPC calls.
+
+Usage:
+ $ python xmlrpcclient.py
+
+The example will make an XML-RPC request to advogato.org and display the result.
+"""
+
+from twisted.web.xmlrpc import Proxy
+from twisted.internet import reactor
+
+def printValue(value):
+ print repr(value)
+ reactor.stop()
+
+def printError(error):
+ print 'error', error
+ reactor.stop()
+
+def capitalize(value):
+ print repr(value)
+ proxy.callRemote('test.capitalize', 'moshe zadka').addCallbacks(printValue, printError)
+
+proxy = Proxy('http://advogato.org/XMLRPC')
+# The callRemote method accepts a method name and an argument list.
+proxy.callRemote('test.sumprod', 2, 5).addCallbacks(capitalize, printError)
+reactor.run()
diff --git a/doc/web/howto/client.html b/doc/web/howto/client.html
new file mode 100644
index 0000000..a344dbc
--- /dev/null
+++ b/doc/web/howto/client.html
@@ -0,0 +1,1088 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation:
+ Using the Twisted Web Client
+ </title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">
+ Using the Twisted Web Client
+ </h1>
+ <div class="toc"><ol><li><a href="#auto0">
+ Overview
+ </a></li><ul><li><a href="#auto1">
+ Prerequisites
+ </a></li></ul><li><a href="#auto2">
+ The Agent
+ </a></li><ul><li><a href="#auto3">Issuing Requests</a></li><li><a href="#auto4">
+ Receiving Responses
+ </a></li><li><a href="#auto5">HTTP over SSL</a></li><li><a href="#auto6">HTTP Persistent Connection</a></li><li><a href="#auto7">Multiple Connections to the Same Server</a></li><li><a href="#auto8">Following redirects</a></li><li><a href="#auto9">Using a HTTP proxy</a></li><li><a href="#auto10">Handling HTTP cookies</a></li><li><a href="#auto11">Automatic Content Encoding Negotiation</a></li></ul><li><a href="#auto12">
+ Conclusion
+ </a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>
+ Overview
+ <a name="auto0"/></h2>
+
+ <p>
+ This document describes how to use the HTTP client included in Twisted
+ Web. After reading it, you should be able to make HTTP and HTTPS
+ requests using Twisted Web. You will be able to specify the request
+ method, headers, and body and you will be able to retrieve the response
+ code, headers, and body.
+ </p>
+
+ <p>
+ A number of higher-level features are also explained, including proxying,
+ automatic content encoding negotiation, and cookie handling.
+ </p>
+
+ <h3>
+ Prerequisites
+ <a name="auto1"/></h3>
+
+ <p>
+
+ This document assumes that you are familiar with <a href="../../core/howto/defer.html" shape="rect">Deferreds and Failures</a>, and <a href="../../core/howto/producers.html" shape="rect">producers and consumers</a>.
+ It also assumes you are familiar with the basic concepts of HTTP, such
+ as requests and responses, methods, headers, and message bodies. The
+ HTTPS section of this document also assumes you are somewhat familiar with
+ SSL and have read about <a href="../../core/howto/ssl.html" shape="rect">using SSL in
+ Twisted</a>.
+ </p>
+
+ <h2>
+ The Agent
+ <a name="auto2"/></h2>
+
+ <h3>Issuing Requests<a name="auto3"/></h3>
+
+ <p>
+ The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.Agent.html" title="twisted.web.client.Agent">twisted.web.client.Agent</a></code> class is the entry
+ point into the client API. Requests are issued using the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.Agent.request.html" title="twisted.web.client.Agent.request">request</a></code> method, which
+ takes as parameters a request method, a request URI, the request headers,
+ and an object which can produce the request body (if there is to be one).
+ The agent is responsible for connection setup. Because of this, it
+ requires a reactor as an argument to its initializer. An example of
+ creating an agent and issuing a request using it might look like this:
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">http_headers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Headers</span>
+
+<span class="py-src-variable">agent</span> = <span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>)
+
+<span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(
+ <span class="py-src-string">'GET'</span>,
+ <span class="py-src-string">'http://example.com/'</span>,
+ <span class="py-src-variable">Headers</span>({<span class="py-src-string">'User-Agent'</span>: [<span class="py-src-string">'Twisted Web Client Example'</span>]}),
+ <span class="py-src-variable">None</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbResponse</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response received'</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbResponse</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbShutdown</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addBoth</span>(<span class="py-src-variable">cbShutdown</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">
+ Issue a request with an Agent
+ - <a href="listings/client/request.py"><span class="filename">listings/client/request.py</span></a></div></div>
+
+ <p>
+ As may be obvious, this issues a new <em>GET</em> request for <em>/</em>
+ to the web server on <code>example.com</code>. <code>Agent</code> is
+ responsible for resolving the hostname into an IP address and connecting
+ to it on port 80 (for <em>HTTP</em> URIs), port 443 (for <em>HTTPS</em>
+ URIs), or on the port number specified in the URI itself. It is also
+ responsible for cleaning up the connection afterwards. This code sends
+ a request which includes one custom header, <em>User-Agent</em>. The
+ last argument passed to <code>Agent.request</code> is <code>None</code>,
+ though, so the request has no body.
+ </p>
+
+ <p>
+ Sending a request which does include a body requires passing an object
+ providing <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.iweb.IBodyProducer.html" title="twisted.web.iweb.IBodyProducer">twisted.web.iweb.IBodyProducer</a></code>
+ to <code>Agent.request</code>. This interface extends the more general
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IPushProducer.html" title="twisted.internet.interfaces.IPushProducer">IPushProducer</a></code>
+ by adding a new <code>length</code> attribute and adding several
+ constraints to the way the producer and consumer interact.
+ </p>
+
+ <ul>
+ <li>
+ The length attribute must be a non-negative integer or the constant
+ <code>twisted.web.iweb.UNKNOWN_LENGTH</code>. If the length is known,
+ it will be used to specify the value for the
+ <em>Content-Length</em> header in the request. If the length is
+ unknown the attribute should be set to <code>UNKNOWN_LENGTH</code>.
+ Since more servers support <em>Content-Length</em>, if a length can be
+ provided it should be.
+ </li>
+
+ <li>
+ An additional method is required on <code>IBodyProducer</code>
+ implementations: <code>startProducing</code>. This method is used to
+ associate a consumer with the producer. It should return a
+ <code>Deferred</code> which fires when all data has been produced.
+ </li>
+
+ <li>
+ <code>IBodyProducer</code> implementations should never call the
+ consumer's <code>unregisterProducer</code> method. Instead, when it
+ has produced all of the data it is going to produce, it should only
+ fire the <code>Deferred</code> returned by <code>startProducing</code>.
+ </li>
+ </ul>
+
+ <p>
+ For additional details about the requirements of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.iweb.IBodyProducer.html" title="twisted.web.iweb.IBodyProducer">IBodyProducer</a></code> implementations, see
+ the API documentation.
+ </p>
+
+ <p>
+ Here's a simple <code>IBodyProducer</code> implementation which
+ writes an in-memory string to the consumer:
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">succeed</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">iweb</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IBodyProducer</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">StringProducer</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IBodyProducer</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">body</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">body</span> = <span class="py-src-variable">body</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span> = <span class="py-src-variable">len</span>(<span class="py-src-variable">body</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startProducing</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">consumer</span>):
+ <span class="py-src-variable">consumer</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">body</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">succeed</span>(<span class="py-src-variable">None</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">pauseProducing</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">pass</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopProducing</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">pass</span>
+</pre><div class="caption">
+ A string-based body producer.
+ - <a href="listings/client/stringprod.py"><span class="filename">listings/client/stringprod.py</span></a></div></div>
+
+ <p>
+ This producer can be used to issue a request with a body:
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">http_headers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Headers</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">stringprod</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">StringProducer</span>
+
+<span class="py-src-variable">agent</span> = <span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>)
+<span class="py-src-variable">body</span> = <span class="py-src-variable">StringProducer</span>(<span class="py-src-string">&quot;hello, world&quot;</span>)
+<span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(
+ <span class="py-src-string">'GET'</span>,
+ <span class="py-src-string">'http://example.com/'</span>,
+ <span class="py-src-variable">Headers</span>({<span class="py-src-string">'User-Agent'</span>: [<span class="py-src-string">'Twisted Web Client Example'</span>],
+ <span class="py-src-string">'Content-Type'</span>: [<span class="py-src-string">'text/x-greeting'</span>]}),
+ <span class="py-src-variable">body</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbResponse</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response received'</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbResponse</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbShutdown</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addBoth</span>(<span class="py-src-variable">cbShutdown</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">
+ Issue a request with a body.
+ - <a href="listings/client/sendbody.py"><span class="filename">listings/client/sendbody.py</span></a></div></div>
+
+ <p>
+ If you want to upload a file or you just have some data in a string, you
+ don't have to copy <code>StringProducer</code> though. Instead, you can
+ use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.FileBodyProducer.html" title="twisted.web.client.FileBodyProducer">FileBodyProducer</a></code>.
+ This <code>IBodyProducer</code> implementation works with any file-like
+ object (so use it with a <code>StringIO</code> if your upload data is
+ already in memory as a string); the idea is the same
+ as <code>StringProducer</code> from the previous example, but with a
+ little extra code to only send data as fast as the server will take it.
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">StringIO</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">StringIO</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">http_headers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Headers</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FileBodyProducer</span>
+
+<span class="py-src-variable">agent</span> = <span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>)
+<span class="py-src-variable">body</span> = <span class="py-src-variable">FileBodyProducer</span>(<span class="py-src-variable">StringIO</span>(<span class="py-src-string">&quot;hello, world&quot;</span>))
+<span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(
+ <span class="py-src-string">'GET'</span>,
+ <span class="py-src-string">'http://example.com/'</span>,
+ <span class="py-src-variable">Headers</span>({<span class="py-src-string">'User-Agent'</span>: [<span class="py-src-string">'Twisted Web Client Example'</span>],
+ <span class="py-src-string">'Content-Type'</span>: [<span class="py-src-string">'text/x-greeting'</span>]}),
+ <span class="py-src-variable">body</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbResponse</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response received'</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbResponse</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbShutdown</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addBoth</span>(<span class="py-src-variable">cbShutdown</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">
+ Another way to issue a request with a body.
+ - <a href="listings/client/filesendbody.py"><span class="filename">listings/client/filesendbody.py</span></a></div></div>
+
+ <p>
+ <code>FileBodyProducer</code> closes the file when it no longer needs it.
+ </p>
+
+ <h3>
+ Receiving Responses
+ <a name="auto4"/></h3>
+
+ <p>
+ So far, the examples have demonstrated how to issue a request. However,
+ they have ignored the response, except for showing that it is a
+ <code>Deferred</code> which seems to fire when the response has been
+ received. Next we'll cover what that response is and how to interpret
+ it.
+ </p>
+
+ <p>
+ <code>Agent.request</code>, as with most <code>Deferred</code>-returning
+ APIs, can return a <code>Deferred</code> which fires with a
+ <code>Failure</code>. If the request fails somehow, this will be
+ reflected with a failure. This may be due to a problem looking up the
+ host IP address, or it may be because the HTTP server is not accepting
+ connections, or it may be because of a problem parsing the response, or
+ any other problem which arises which prevents the response from being
+ received. It does <em>not</em> include responses with an error status.
+ </p>
+
+ <p>
+ If the request succeeds, though, the <code>Deferred</code> will fire with
+ a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.Response.html" title="twisted.web.client.Response">Response</a></code>. This
+ happens as soon as all the response headers have been received. It
+ happens before any of the response body, if there is one, is processed.
+ The <code>Response</code> object has several attributes giving the
+ response information: its code, version, phrase, and headers, as well as
+ the length of the body to expect. The <code>Response</code> object also
+ has a method which makes the response body available: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.Response.deliverBody.html" title="twisted.web.client.Response.deliverBody">deliverBody</a></code>. Using the
+ attributes of the response object and this method, here's an example which
+ displays part of the response to a request:
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">pprint</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pformat</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Deferred</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">http_headers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Headers</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">BeginningPrinter</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">finished</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">finished</span> = <span class="py-src-variable">finished</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span> = <span class="py-src-number">1024</span> * <span class="py-src-number">10</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">bytes</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span>:
+ <span class="py-src-variable">display</span> = <span class="py-src-variable">bytes</span>[:<span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span>]
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Some data received:'</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">display</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span> -= <span class="py-src-variable">len</span>(<span class="py-src-variable">display</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Finished receiving body:'</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">getErrorMessage</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">finished</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">None</span>)
+
+<span class="py-src-variable">agent</span> = <span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>)
+<span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(
+ <span class="py-src-string">'GET'</span>,
+ <span class="py-src-string">'http://example.com/'</span>,
+ <span class="py-src-variable">Headers</span>({<span class="py-src-string">'User-Agent'</span>: [<span class="py-src-string">'Twisted Web Client Example'</span>]}),
+ <span class="py-src-variable">None</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbRequest</span>(<span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response version:'</span>, <span class="py-src-variable">response</span>.<span class="py-src-variable">version</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response code:'</span>, <span class="py-src-variable">response</span>.<span class="py-src-variable">code</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response phrase:'</span>, <span class="py-src-variable">response</span>.<span class="py-src-variable">phrase</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response headers:'</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">pformat</span>(<span class="py-src-variable">list</span>(<span class="py-src-variable">response</span>.<span class="py-src-variable">headers</span>.<span class="py-src-variable">getAllRawHeaders</span>()))
+ <span class="py-src-variable">finished</span> = <span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">response</span>.<span class="py-src-variable">deliverBody</span>(<span class="py-src-variable">BeginningPrinter</span>(<span class="py-src-variable">finished</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">finished</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbRequest</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbShutdown</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addBoth</span>(<span class="py-src-variable">cbShutdown</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">
+ Inspect the response.
+ - <a href="listings/client/response.py"><span class="filename">listings/client/response.py</span></a></div></div>
+
+ <p>
+ The <code>BeginningPrinter</code> protocol in this example is passed to
+ <code>Response.deliverBody</code> and the response body is then delivered
+ to its <code>dataReceived</code> method as it arrives. When the body has
+ been completely delivered, the protocol's <code>connectionLost</code>
+ method is called. It is important to inspect the <code>Failure</code>
+ passed to <code>connectionLost</code>. If the response body has been
+ completely received, the failure will wrap a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.ResponseDone.html" title="twisted.web.client.ResponseDone">twisted.web.client.ResponseDone</a></code> exception. This
+ indicates that it is <em>known</em> that all data has been received. It
+ is also possible for the failure to wrap a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.http.PotentialDataLoss.html" title="twisted.web.http.PotentialDataLoss">twisted.web.http.PotentialDataLoss</a></code> exception: this
+ indicates that the server framed the response such that there is no way
+ to know when the entire response body has been received. Only
+ HTTP/1.0 servers should behave this way. Finally, it is possible for
+ the exception to be of another type, indicating guaranteed data loss for
+ some reason (a lost connection, a memory error, etc).
+ </p>
+
+ <p>
+ Just as protocols associated with a TCP connection are given a transport,
+ so will be a protocol passed to <code>deliverBody</code>. Since it makes
+ no sense to write more data to the connection at this stage of the
+ request, though, the transport <em>only</em> provides <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IPushProducer.html" title="twisted.internet.interfaces.IPushProducer">IPushProducer</a></code>. This allows the
+ protocol to control the flow of the response data: a call to the
+ transport's <code>pauseProducing</code> method will pause delivery; a
+ later call to <code>resumeProducing</code> will resume it. If it is
+ decided that the rest of the response body is not desired,
+ <code>stopProducing</code> can be used to stop delivery permanently;
+ after this, the protocol's <code>connectionLost</code> method will be
+ called.
+ </p>
+
+ <p>
+ An important thing to keep in mind is that the body will only be read
+ from the connection after <code>Response.deliverBody</code> is called.
+ This also means that the connection will remain open until this is done
+ (and the body read). So, in general, any response with a body
+ <em>must</em> have that body read using <code>deliverBody</code>. If the
+ application is not interested in the body, it should issue a
+ <em>HEAD</em> request or use a protocol which immediately calls
+ <code>stopProducing</code> on its transport.
+ </p>
+
+ <h3>HTTP over SSL<a name="auto5"/></h3>
+
+ <p>
+ Everything you've read so far applies whether the scheme of the request
+ URI is <em>HTTP</em> or <em>HTTPS</em>. However, to control the SSL
+ negotiation performed when an <em>HTTPS</em> URI is requested, there's
+ one extra object to pay attention to: the SSL context factory.
+ </p>
+
+ <p>
+ <code>Agent</code>'s constructor takes an optional second argument, a
+ context factory. This is an object like the context factory described
+ in <a href="../../core/howto/ssl.html" shape="rect">Using SSL in Twisted</a> but has
+ one small difference. The <code>getContext</code> method of this factory
+ accepts the address from the URL being requested. This allows it to
+ return a context object which verifies that the server's certificate
+ matches the URL being requested.
+ </p>
+
+ <p>
+ Here's an example which shows how to use <code>Agent</code> to request
+ an <em>HTTPS</em> URL with no certificate verification.
+ </p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">err</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">ssl</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ClientContextFactory</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">WebClientContextFactory</span>(<span class="py-src-parameter">ClientContextFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getContext</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">hostname</span>, <span class="py-src-parameter">port</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">ClientContextFactory</span>.<span class="py-src-variable">getContext</span>(<span class="py-src-variable">self</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">display</span>(<span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Received response&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">response</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">contextFactory</span> = <span class="py-src-variable">WebClientContextFactory</span>()
+ <span class="py-src-variable">agent</span> = <span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">contextFactory</span>)
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(<span class="py-src-string">&quot;GET&quot;</span>, <span class="py-src-string">&quot;https://example.com/&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">display</span>, <span class="py-src-variable">err</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">ignored</span>: <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">main</span>()
+</pre>
+
+ <p>
+ The important point to notice here is that <code>getContext</code> now
+ accepts two arguments, a hostname and a port number. These two arguments,
+ a <code>str</code> and an <code>int</code>, give the address to which a
+ connection is being established to request an HTTPS URL. Because an agent
+ might make multiple requests over a single connection,
+ <code>getContext</code> may not be called once for each request. A second
+ or later request for a URL with the same hostname as a previous request
+ may re-use an existing connection, and therefore will re-use the
+ previously returned context object.
+ </p>
+
+ <p>
+ To configure SSL options or enable certificate verification or hostname
+ checking, provide a context factory which creates suitably configured
+ context objects.
+ </p>
+
+ <h3>HTTP Persistent Connection<a name="auto6"/></h3>
+
+ <p>
+ HTTP persistent connections use the same TCP connection to send and
+ receive multiple HTTP requests/responses. This reduces latency and TCP
+ connection establishment overhead.
+ </p>
+
+ <p>
+ The constructor of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.Agent.html" title="twisted.web.client.Agent">twisted.web.client.Agent</a></code>
+ takes an optional parameter pool, which should be an instance
+ of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.HTTPConnectionPool.html" title="twisted.web.client.HTTPConnectionPool">HTTPConnectionPool</a></code>, which will be used
+ to manage the connections. If the pool is created with the
+ parameter <code>persistent</code> set to <code>True</code> (the
+ default), it will not close connections when the request is done, and
+ instead hold them in its cache to be re-used.
+ </p>
+
+ <p>
+ Here's an example which sends requests over a persistent connection:
+ </p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Deferred</span>, <span class="py-src-variable">DeferredList</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>, <span class="py-src-variable">HTTPConnectionPool</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IgnoreBody</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">deferred</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">deferred</span> = <span class="py-src-variable">deferred</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">bytes</span>):
+ <span class="py-src-keyword">pass</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">deferred</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">None</span>)
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbRequest</span>(<span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response code:'</span>, <span class="py-src-variable">response</span>.<span class="py-src-variable">code</span>
+ <span class="py-src-variable">finished</span> = <span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">response</span>.<span class="py-src-variable">deliverBody</span>(<span class="py-src-variable">IgnoreBody</span>(<span class="py-src-variable">finished</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">finished</span>
+
+<span class="py-src-variable">pool</span> = <span class="py-src-variable">HTTPConnectionPool</span>(<span class="py-src-variable">reactor</span>)
+<span class="py-src-variable">agent</span> = <span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">pool</span>=<span class="py-src-variable">pool</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">requestGet</span>(<span class="py-src-parameter">url</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(<span class="py-src-string">'GET'</span>, <span class="py-src-variable">url</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbRequest</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-comment"># Two requests to the same host:</span>
+<span class="py-src-variable">d</span> = <span class="py-src-variable">requestGet</span>(<span class="py-src-string">'http://localhost:8080/foo'</span>).<span class="py-src-variable">addCallback</span>(
+ <span class="py-src-keyword">lambda</span> <span class="py-src-variable">ign</span>: <span class="py-src-variable">requestGet</span>(<span class="py-src-string">&quot;http://localhost:8080/bar&quot;</span>))
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbShutdown</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbShutdown</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>
+ Here, the two requests are to the same host, one after the each
+ other. In most cases, the same connection will be used for the second
+ request, instead of two different connections when using a
+ non-persistent pool.
+ </p>
+
+ <h3>Multiple Connections to the Same Server<a name="auto7"/></h3>
+
+ <p>
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.HTTPConnectionPool.html" title="twisted.web.client.HTTPConnectionPool">twisted.web.client.HTTPConnectionPool</a></code> instances
+ have an attribute
+ called <code class="python">maxPersistentPerHost</code> which limits the
+ number of cached persistent connections to the same server. The default
+ value is 2. This is effective only when the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.HTTPConnectionPool.persistent.html" title="twisted.web.client.HTTPConnectionPool.persistent">persistent</a></code> option is
+ True. You can change the value like bellow:
+ </p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">HTTPConnectionPool</span>
+
+<span class="py-src-variable">pool</span> = <span class="py-src-variable">HTTPConnectionPool</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">persistent</span>=<span class="py-src-variable">True</span>)
+<span class="py-src-variable">pool</span>.<span class="py-src-variable">maxPersistentPerHost</span> = <span class="py-src-number">1</span>
+</pre>
+
+ <p>
+ With the default value of 2, the pool keeps around two connections to
+ the same host at most. Eventually the cached persistent connections will
+ be closed, by default after 240 seconds; you can change this timeout
+ value with the <code class="python">cachedConnectionTimeout</code>
+ attribute of the pool. To force all connections to close use
+ the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.HTTPConnectionPool.closeCachedConnections.html" title="twisted.web.client.HTTPConnectionPool.closeCachedConnections">closeCachedConnections</a></code>
+ method.
+ </p>
+
+
+ <h4>Automatic Retries</h4>
+
+ <p>If a request fails without getting a response, and the request is
+ something that hopefully can be retried without having any side-effects
+ (e.g. a request with method GET), it will be retried automatically when
+ sending a request over a previously-cached persistent connection. You can
+ disable this behavior by setting <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.HTTPConnectionPool.retryAutomatically.html" title="twisted.web.client.HTTPConnectionPool.retryAutomatically">retryAutomatically</a></code>
+ to <code>False</code>. Note that each request will only be retried
+ once.</p>
+
+
+ <h3>Following redirects<a name="auto8"/></h3>
+
+ <p>
+ By itself, <code>Agent</code> doesn't follow HTTP redirects (responses
+ with 301, 302, 303, 307 status codes and a <code>location</code> header
+ field). You need to use the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.RedirectAgent.html" title="twisted.web.client.RedirectAgent">twisted.web.client.RedirectAgent</a></code> class to do so. It
+ implements a rather strict behavior of the RFC, meaning it will redirect
+ 301 and 302 as 307, only on <code>GET</code> and <code>HEAD</code>
+ requests.
+ </p>
+ <p>
+ The following example shows how to have a redirect-enabled agent.
+ </p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">err</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>, <span class="py-src-variable">RedirectAgent</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">display</span>(<span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Received response&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">response</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">agent</span> = <span class="py-src-variable">RedirectAgent</span>(<span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>))
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(<span class="py-src-string">&quot;GET&quot;</span>, <span class="py-src-string">&quot;http://example.com/&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">display</span>, <span class="py-src-variable">err</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">ignored</span>: <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">main</span>()
+</pre>
+
+ <h3>Using a HTTP proxy<a name="auto9"/></h3>
+
+ <p>
+ To be able to use HTTP proxies with an agent, you can use the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.ProxyAgent.html" title="twisted.web.client.ProxyAgent">twisted.web.client.ProxyAgent</a></code> class. It supports the
+ same interface as <code>Agent</code>, but takes the endpoint of the proxy
+ as initializer argument.
+ </p>
+
+ <p>
+ Here's an example demonstrating the use of an HTTP proxy running on
+ localhost:8000.
+ </p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">err</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ProxyAgent</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">endpoints</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">TCP4ClientEndpoint</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">display</span>(<span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Received response&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">response</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">endpoint</span> = <span class="py-src-variable">TCP4ClientEndpoint</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8000</span>)
+ <span class="py-src-variable">agent</span> = <span class="py-src-variable">ProxyAgent</span>(<span class="py-src-variable">endpoint</span>)
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(<span class="py-src-string">&quot;GET&quot;</span>, <span class="py-src-string">&quot;https://example.com/&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">display</span>, <span class="py-src-variable">err</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">ignored</span>: <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">main</span>()
+</pre>
+
+ <p>
+ Please refer to the <a href="../../core/howto/endpoints.html" shape="rect">endpoints documentation</a> for
+ more information about how they work and the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.endpoints.html" title="twisted.internet.endpoints">twisted.internet.endpoints</a></code> API documentation to learn
+ what other kinds of endpoints exist.
+ </p>
+
+ <h3>Handling HTTP cookies<a name="auto10"/></h3>
+
+ <p>
+ An existing agent instance can be wrapped with
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.CookieAgent.html" title="twisted.web.client.CookieAgent">twisted.web.client.CookieAgent</a></code> to automatically
+ store, send and track HTTP cookies. A <code>CookieJar</code>
+ instance, from the Python standard library module
+ <a href="http://docs.python.org/library/cookielib.html" shape="rect">cookielib</a>, is
+ used to store the cookie information. An example of using
+ <code>CookieAgent</code> to perform a request and display the collected
+ cookies might look like this:
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">cookielib</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">CookieJar</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>, <span class="py-src-variable">CookieAgent</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">displayCookies</span>(<span class="py-src-parameter">response</span>, <span class="py-src-parameter">cookieJar</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Received response'</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">response</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Cookies:'</span>, <span class="py-src-variable">len</span>(<span class="py-src-variable">cookieJar</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">cookie</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">cookieJar</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">cookie</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">cookieJar</span> = <span class="py-src-variable">CookieJar</span>()
+ <span class="py-src-variable">agent</span> = <span class="py-src-variable">CookieAgent</span>(<span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>), <span class="py-src-variable">cookieJar</span>)
+
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(<span class="py-src-string">'GET'</span>, <span class="py-src-string">'http://www.google.com/'</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">displayCookies</span>, <span class="py-src-variable">cookieJar</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">ignored</span>: <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">
+ Storing cookies with CookieAgent
+ - <a href="listings/client/cookies.py"><span class="filename">listings/client/cookies.py</span></a></div></div>
+
+ <h3>Automatic Content Encoding Negotiation<a name="auto11"/></h3>
+
+ <p>
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.ContentDecoderAgent.html" title="twisted.web.client.ContentDecoderAgent">twisted.web.client.ContentDecoderAgent</a></code> adds
+ support for sending <em>Accept-Encoding</em> request headers and
+ interpreting <em>Content-Encoding</em> response headers. These headers
+ allow the server to encode the response body somehow, typically with some
+ compression scheme to save on transfer
+ costs. <code>ContentDecoderAgent</code> provides this functionality as a
+ wrapper around an existing agent instance. Together with one or more
+ decoder objects (such as
+ <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.client.GzipDecoder.html" title="twisted.web.client.GzipDecoder">twisted.web.client.GzipDecoder</a></code>), this wrapper
+ automatically negotiates an encoding to use and decodes the response body
+ accordingly. To application code using such an agent, there is no visible
+ difference in the data delivered.
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Deferred</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>, <span class="py-src-variable">ContentDecoderAgent</span>, <span class="py-src-variable">GzipDecoder</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">BeginningPrinter</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">finished</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">finished</span> = <span class="py-src-variable">finished</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span> = <span class="py-src-number">1024</span> * <span class="py-src-number">10</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">bytes</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span>:
+ <span class="py-src-variable">display</span> = <span class="py-src-variable">bytes</span>[:<span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span>]
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Some data received:'</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">display</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span> -= <span class="py-src-variable">len</span>(<span class="py-src-variable">display</span>)
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Finished receiving body:'</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">type</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">value</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">finished</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">None</span>)
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printBody</span>(<span class="py-src-parameter">response</span>):
+ <span class="py-src-variable">finished</span> = <span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">response</span>.<span class="py-src-variable">deliverBody</span>(<span class="py-src-variable">BeginningPrinter</span>(<span class="py-src-variable">finished</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">finished</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">agent</span> = <span class="py-src-variable">ContentDecoderAgent</span>(<span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>), [(<span class="py-src-string">'gzip'</span>, <span class="py-src-variable">GzipDecoder</span>)])
+
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(<span class="py-src-string">'GET'</span>, <span class="py-src-string">'http://www.yahoo.com/'</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printBody</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">ignored</span>: <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">
+ Requesting gzip encoded responses, with automatic decompression.
+ - <a href="listings/client/gzipdecoder.py"><span class="filename">listings/client/gzipdecoder.py</span></a></div></div>
+
+ <p>
+ Implementing support for new content encodings is as simple as writing a
+ new class like <code>GzipDecoder</code> that can decode a response using
+ the new encoding. As there are not many content encodings in widespread
+ use, gzip is the only encoding supported by Twisted itself.
+ </p>
+
+ <h2>
+ Conclusion
+ <a name="auto12"/></h2>
+
+ <p>
+ You should now understand the basics of the Twisted Web HTTP client. In
+ particular, you should understand:
+ </p>
+
+ <ul>
+ <li>
+ How to issue requests with arbitrary methods, headers, and bodies.
+ </li>
+ <li>
+ How to access the response version, code, phrase, headers, and body.
+ </li>
+ <li>
+ How to store, send, and track cookies.
+ </li>
+ <li>
+ How to control the streaming of the response body.
+ </li>
+ <li>
+ How to enable the HTTP persistent connection, and control the
+ number of connections.
+ </li>
+ </ul>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/glossary.html b/doc/web/howto/glossary.html
new file mode 100644
index 0000000..34fe7b7
--- /dev/null
+++ b/doc/web/howto/glossary.html
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Glossary</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Glossary</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <p class="note"><strong>Note: </strong>This glossary is very incomplete. Contributions are
+ welcome.</p>
+
+ <dl>
+ <dt><a name="resource" shape="rect">resource</a></dt>
+ <dd>
+ An object accessible via HTTP at one or more URIs. In Twisted Web,
+ a resource is represented by an object which provides <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.IResource.html" title="twisted.web.resource.IResource">twisted.web.resource.IResource</a></code> and most often is
+ a subclass of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">twisted.web.resource.Resource</a></code>. For example, here
+ is a resource which represents a simple HTML greeting.
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Greeting</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Hello, world.&quot;</span>
+</pre>
+ </dd>
+ </dl>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/index.html b/doc/web/howto/index.html
new file mode 100644
index 0000000..874ee51
--- /dev/null
+++ b/doc/web/howto/index.html
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Web Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Web Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+
+<li>Introduction
+ <ul>
+ <li><a href="web-overview.html" shape="rect">Overview of Twisted Web</a></li>
+ </ul>
+</li>
+
+<li>Web Applications
+ <ul>
+ <li><a href="using-twistedweb.html" shape="rect">Using twisted.web</a></li>
+ <li><a href="web-development.html" shape="rect">Web application development</a></li>
+ <li><a href="twisted-templates.html" shape="rect">HTML Templating with
+ twisted.web.template</a></li>
+ <li><a href="xmlrpc.html" shape="rect">XML-RPC and SOAP</a></li>
+ <li><a href="web-in-60/index.html" shape="rect">Twisted Web in 60 Seconds: A
+ series of short, complete examples using twisted.web</a></li>
+ <li><a href="resource-templates.html" shape="rect">Quixote resource templates</a></li>
+ </ul>
+</li>
+
+<li>Other
+ <ul>
+ <li><a href="client.html" shape="rect">Using the Twisted Web Client</a></li>
+ </ul>
+</li>
+
+<li>Appendix
+ <ul>
+ <li><a href="glossary.html" shape="rect">Glossary</a></li>
+ </ul>
+</li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/listings/client/cookies.py b/doc/web/howto/listings/client/cookies.py
new file mode 100644
index 0000000..80f84c2
--- /dev/null
+++ b/doc/web/howto/listings/client/cookies.py
@@ -0,0 +1,25 @@
+from cookielib import CookieJar
+
+from twisted.internet import reactor
+from twisted.python import log
+from twisted.web.client import Agent, CookieAgent
+
+def displayCookies(response, cookieJar):
+ print 'Received response'
+ print response
+ print 'Cookies:', len(cookieJar)
+ for cookie in cookieJar:
+ print cookie
+
+def main():
+ cookieJar = CookieJar()
+ agent = CookieAgent(Agent(reactor), cookieJar)
+
+ d = agent.request('GET', 'http://www.google.com/')
+ d.addCallback(displayCookies, cookieJar)
+ d.addErrback(log.err)
+ d.addCallback(lambda ignored: reactor.stop())
+ reactor.run()
+
+if __name__ == "__main__":
+ main()
diff --git a/doc/web/howto/listings/client/filesendbody.py b/doc/web/howto/listings/client/filesendbody.py
new file mode 100644
index 0000000..ed26238
--- /dev/null
+++ b/doc/web/howto/listings/client/filesendbody.py
@@ -0,0 +1,26 @@
+from StringIO import StringIO
+
+from twisted.internet import reactor
+from twisted.web.client import Agent
+from twisted.web.http_headers import Headers
+
+from twisted.web.client import FileBodyProducer
+
+agent = Agent(reactor)
+body = FileBodyProducer(StringIO("hello, world"))
+d = agent.request(
+ 'GET',
+ 'http://example.com/',
+ Headers({'User-Agent': ['Twisted Web Client Example'],
+ 'Content-Type': ['text/x-greeting']}),
+ body)
+
+def cbResponse(ignored):
+ print 'Response received'
+d.addCallback(cbResponse)
+
+def cbShutdown(ignored):
+ reactor.stop()
+d.addBoth(cbShutdown)
+
+reactor.run()
diff --git a/doc/web/howto/listings/client/gzipdecoder.py b/doc/web/howto/listings/client/gzipdecoder.py
new file mode 100644
index 0000000..dc6af43
--- /dev/null
+++ b/doc/web/howto/listings/client/gzipdecoder.py
@@ -0,0 +1,43 @@
+from twisted.python import log
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred
+from twisted.internet.protocol import Protocol
+from twisted.web.client import Agent, ContentDecoderAgent, GzipDecoder
+
+class BeginningPrinter(Protocol):
+ def __init__(self, finished):
+ self.finished = finished
+ self.remaining = 1024 * 10
+
+
+ def dataReceived(self, bytes):
+ if self.remaining:
+ display = bytes[:self.remaining]
+ print 'Some data received:'
+ print display
+ self.remaining -= len(display)
+
+
+ def connectionLost(self, reason):
+ print 'Finished receiving body:', reason.type, reason.value
+ self.finished.callback(None)
+
+
+
+def printBody(response):
+ finished = Deferred()
+ response.deliverBody(BeginningPrinter(finished))
+ return finished
+
+
+def main():
+ agent = ContentDecoderAgent(Agent(reactor), [('gzip', GzipDecoder)])
+
+ d = agent.request('GET', 'http://www.yahoo.com/')
+ d.addCallback(printBody)
+ d.addErrback(log.err)
+ d.addCallback(lambda ignored: reactor.stop())
+ reactor.run()
+
+if __name__ == "__main__":
+ main()
diff --git a/doc/web/howto/listings/client/request.py b/doc/web/howto/listings/client/request.py
new file mode 100644
index 0000000..4931863
--- /dev/null
+++ b/doc/web/howto/listings/client/request.py
@@ -0,0 +1,21 @@
+from twisted.internet import reactor
+from twisted.web.client import Agent
+from twisted.web.http_headers import Headers
+
+agent = Agent(reactor)
+
+d = agent.request(
+ 'GET',
+ 'http://example.com/',
+ Headers({'User-Agent': ['Twisted Web Client Example']}),
+ None)
+
+def cbResponse(ignored):
+ print 'Response received'
+d.addCallback(cbResponse)
+
+def cbShutdown(ignored):
+ reactor.stop()
+d.addBoth(cbShutdown)
+
+reactor.run()
diff --git a/doc/web/howto/listings/client/response.py b/doc/web/howto/listings/client/response.py
new file mode 100644
index 0000000..6b3547c
--- /dev/null
+++ b/doc/web/howto/listings/client/response.py
@@ -0,0 +1,47 @@
+from pprint import pformat
+
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred
+from twisted.internet.protocol import Protocol
+from twisted.web.client import Agent
+from twisted.web.http_headers import Headers
+
+class BeginningPrinter(Protocol):
+ def __init__(self, finished):
+ self.finished = finished
+ self.remaining = 1024 * 10
+
+ def dataReceived(self, bytes):
+ if self.remaining:
+ display = bytes[:self.remaining]
+ print 'Some data received:'
+ print display
+ self.remaining -= len(display)
+
+ def connectionLost(self, reason):
+ print 'Finished receiving body:', reason.getErrorMessage()
+ self.finished.callback(None)
+
+agent = Agent(reactor)
+d = agent.request(
+ 'GET',
+ 'http://example.com/',
+ Headers({'User-Agent': ['Twisted Web Client Example']}),
+ None)
+
+def cbRequest(response):
+ print 'Response version:', response.version
+ print 'Response code:', response.code
+ print 'Response phrase:', response.phrase
+ print 'Response headers:'
+ print pformat(list(response.headers.getAllRawHeaders()))
+ finished = Deferred()
+ response.deliverBody(BeginningPrinter(finished))
+ return finished
+d.addCallback(cbRequest)
+
+def cbShutdown(ignored):
+ reactor.stop()
+d.addBoth(cbShutdown)
+
+reactor.run()
diff --git a/doc/web/howto/listings/client/sendbody.py b/doc/web/howto/listings/client/sendbody.py
new file mode 100644
index 0000000..31cac8f
--- /dev/null
+++ b/doc/web/howto/listings/client/sendbody.py
@@ -0,0 +1,24 @@
+from twisted.internet import reactor
+from twisted.web.client import Agent
+from twisted.web.http_headers import Headers
+
+from stringprod import StringProducer
+
+agent = Agent(reactor)
+body = StringProducer("hello, world")
+d = agent.request(
+ 'GET',
+ 'http://example.com/',
+ Headers({'User-Agent': ['Twisted Web Client Example'],
+ 'Content-Type': ['text/x-greeting']}),
+ body)
+
+def cbResponse(ignored):
+ print 'Response received'
+d.addCallback(cbResponse)
+
+def cbShutdown(ignored):
+ reactor.stop()
+d.addBoth(cbShutdown)
+
+reactor.run()
diff --git a/doc/web/howto/listings/client/stringprod.py b/doc/web/howto/listings/client/stringprod.py
new file mode 100644
index 0000000..da2b5cd
--- /dev/null
+++ b/doc/web/howto/listings/client/stringprod.py
@@ -0,0 +1,21 @@
+from zope.interface import implements
+
+from twisted.internet.defer import succeed
+from twisted.web.iweb import IBodyProducer
+
+class StringProducer(object):
+ implements(IBodyProducer)
+
+ def __init__(self, body):
+ self.body = body
+ self.length = len(body)
+
+ def startProducing(self, consumer):
+ consumer.write(self.body)
+ return succeed(None)
+
+ def pauseProducing(self):
+ pass
+
+ def stopProducing(self):
+ pass
diff --git a/doc/web/howto/listings/element_1.py b/doc/web/howto/listings/element_1.py
new file mode 100644
index 0000000..bb5e89a
--- /dev/null
+++ b/doc/web/howto/listings/element_1.py
@@ -0,0 +1,13 @@
+from twisted.web.template import Element, renderer, XMLFile
+from twisted.python.filepath import FilePath
+
+class ExampleElement(Element):
+ loader = XMLFile(FilePath('template-1.xml'))
+
+ @renderer
+ def header(self, request, tag):
+ return tag('Header.')
+
+ @renderer
+ def footer(self, request, tag):
+ return tag('Footer.')
diff --git a/doc/web/howto/listings/element_2.py b/doc/web/howto/listings/element_2.py
new file mode 100644
index 0000000..a4c0271
--- /dev/null
+++ b/doc/web/howto/listings/element_2.py
@@ -0,0 +1,13 @@
+from twisted.web.template import Element, renderer, XMLFile, tags
+from twisted.python.filepath import FilePath
+
+class ExampleElement(Element):
+ loader = XMLFile(FilePath('template-1.xml'))
+
+ @renderer
+ def header(self, request, tag):
+ return tag(tags.b('Header.'))
+
+ @renderer
+ def footer(self, request, tag):
+ return tag(tags.b('Footer.'))
diff --git a/doc/web/howto/listings/element_3.py b/doc/web/howto/listings/element_3.py
new file mode 100644
index 0000000..46c2181
--- /dev/null
+++ b/doc/web/howto/listings/element_3.py
@@ -0,0 +1,13 @@
+from twisted.web.template import Element, renderer, XMLFile, tags
+from twisted.python.filepath import FilePath
+
+class ExampleElement(Element):
+ loader = XMLFile(FilePath('template-1.xml'))
+
+ @renderer
+ def header(self, request, tag):
+ return tag(tags.p('Header.'), id='header')
+
+ @renderer
+ def footer(self, request, tag):
+ return tag(tags.p('Footer.'), id='footer')
diff --git a/doc/web/howto/listings/iteration-1.py b/doc/web/howto/listings/iteration-1.py
new file mode 100644
index 0000000..e5ffcd3
--- /dev/null
+++ b/doc/web/howto/listings/iteration-1.py
@@ -0,0 +1,17 @@
+from twisted.web.template import Element, renderer, XMLFile, flattenString
+from twisted.python.filepath import FilePath
+
+class WidgetsElement(Element):
+ loader = XMLFile(FilePath('iteration-1.xml'))
+
+ widgetData = ['gadget', 'contraption', 'gizmo', 'doohickey']
+
+ @renderer
+ def widgets(self, request, tag):
+ for widget in self.widgetData:
+ yield tag.clone().fillSlots(widgetName=widget)
+
+def printResult(result):
+ print result
+
+flattenString(None, WidgetsElement()).addCallback(printResult)
diff --git a/doc/web/howto/listings/iteration-1.xml b/doc/web/howto/listings/iteration-1.xml
new file mode 100644
index 0000000..3c9e2c2
--- /dev/null
+++ b/doc/web/howto/listings/iteration-1.xml
@@ -0,0 +1,3 @@
+<ul xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
+ <li t:render="widgets"><t:slot name="widgetName"/></li>
+</ul>
diff --git a/doc/web/howto/listings/iteration-output-1.xml b/doc/web/howto/listings/iteration-output-1.xml
new file mode 100644
index 0000000..edb7f5b
--- /dev/null
+++ b/doc/web/howto/listings/iteration-output-1.xml
@@ -0,0 +1,3 @@
+<ul>
+ <li>gadget</li><li>contraption</li><li>gizmo</li><li>doohickey</li>
+</ul>
diff --git a/doc/web/howto/listings/output-1.html b/doc/web/howto/listings/output-1.html
new file mode 100644
index 0000000..f1c62ff
--- /dev/null
+++ b/doc/web/howto/listings/output-1.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+ <div>Header.</div>
+ <div id="content">
+ <p>Content goes here.</p>
+ </div>
+ <div>Footer.</div>
+</body>
+</html>
diff --git a/doc/web/howto/listings/output-2.html b/doc/web/howto/listings/output-2.html
new file mode 100644
index 0000000..7126b10
--- /dev/null
+++ b/doc/web/howto/listings/output-2.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+ <div><b>Header.</b></div>
+ <div id="content">
+ <p>Content goes here.</p>
+ </div>
+ <div><b>Footer.</b></div>
+</body>
+</html>
diff --git a/doc/web/howto/listings/output-3.html b/doc/web/howto/listings/output-3.html
new file mode 100644
index 0000000..ebe8dba
--- /dev/null
+++ b/doc/web/howto/listings/output-3.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+ <div id="header"><p>Header.</p></div>
+ <div id="content">
+ <p>Content goes here.</p>
+ </div>
+ <div id="footer"><p>Footer.</p></div>
+</body>
+</html>
diff --git a/doc/web/howto/listings/quoting-output.html b/doc/web/howto/listings/quoting-output.html
new file mode 100644
index 0000000..38108e3
--- /dev/null
+++ b/doc/web/howto/listings/quoting-output.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+ <div>&lt;&lt;&lt;Header&gt;&gt;&gt;!</div>
+ <div id="content">
+ <p>Content goes here.</p>
+ </div>
+ <div id="&lt;&quot;fun&quot;&gt;">&gt;&gt;&gt;"Footer!"&lt;&lt;&lt;</div>
+</body>
+</html>
diff --git a/doc/web/howto/listings/quoting_element.py b/doc/web/howto/listings/quoting_element.py
new file mode 100644
index 0000000..a9ab1ff
--- /dev/null
+++ b/doc/web/howto/listings/quoting_element.py
@@ -0,0 +1,13 @@
+from twisted.web.template import Element, renderer, XMLFile
+from twisted.python.filepath import FilePath
+
+class ExampleElement(Element):
+ loader = XMLFile(FilePath('template-1.xml'))
+
+ @renderer
+ def header(self, request, tag):
+ return tag('<<<Header>>>!')
+
+ @renderer
+ def footer(self, request, tag):
+ return tag('>>>"Footer!"<<<', id='<"fun">')
diff --git a/doc/web/howto/listings/render_1.py b/doc/web/howto/listings/render_1.py
new file mode 100644
index 0000000..4709949
--- /dev/null
+++ b/doc/web/howto/listings/render_1.py
@@ -0,0 +1,5 @@
+from twisted.web.template import flattenString
+from element_1 import ExampleElement
+def renderDone(output):
+ print output
+flattenString(None, ExampleElement()).addCallback(renderDone)
diff --git a/doc/web/howto/listings/render_2.py b/doc/web/howto/listings/render_2.py
new file mode 100644
index 0000000..98b5a11
--- /dev/null
+++ b/doc/web/howto/listings/render_2.py
@@ -0,0 +1,5 @@
+from twisted.web.template import flattenString
+from element_2 import ExampleElement
+def renderDone(output):
+ print output
+flattenString(None, ExampleElement()).addCallback(renderDone)
diff --git a/doc/web/howto/listings/render_3.py b/doc/web/howto/listings/render_3.py
new file mode 100644
index 0000000..0058536
--- /dev/null
+++ b/doc/web/howto/listings/render_3.py
@@ -0,0 +1,5 @@
+from twisted.web.template import flattenString
+from element_3 import ExampleElement
+def renderDone(output):
+ print output
+flattenString(None, ExampleElement()).addCallback(renderDone)
diff --git a/doc/web/howto/listings/render_quoting.py b/doc/web/howto/listings/render_quoting.py
new file mode 100644
index 0000000..78b7208
--- /dev/null
+++ b/doc/web/howto/listings/render_quoting.py
@@ -0,0 +1,5 @@
+from twisted.web.template import flattenString
+from quoting_element import ExampleElement
+def renderDone(output):
+ print output
+flattenString(None, ExampleElement()).addCallback(renderDone)
diff --git a/doc/web/howto/listings/render_slots_attrs.py b/doc/web/howto/listings/render_slots_attrs.py
new file mode 100644
index 0000000..da8ff55
--- /dev/null
+++ b/doc/web/howto/listings/render_slots_attrs.py
@@ -0,0 +1,5 @@
+from twisted.web.template import flattenString
+from slots_attributes_1 import ExampleElement
+def renderDone(output):
+ print output
+flattenString(None, ExampleElement()).addCallback(renderDone)
diff --git a/doc/web/howto/listings/render_transparent.py b/doc/web/howto/listings/render_transparent.py
new file mode 100644
index 0000000..4fa5d59
--- /dev/null
+++ b/doc/web/howto/listings/render_transparent.py
@@ -0,0 +1,5 @@
+from twisted.web.template import flattenString
+from transparent_element import ExampleElement
+def renderDone(output):
+ print output
+flattenString(None, ExampleElement()).addCallback(renderDone)
diff --git a/doc/web/howto/listings/slots-attributes-1.xml b/doc/web/howto/listings/slots-attributes-1.xml
new file mode 100644
index 0000000..9b24ade
--- /dev/null
+++ b/doc/web/howto/listings/slots-attributes-1.xml
@@ -0,0 +1,6 @@
+<div xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
+ t:render="person_profile"
+ class="profile">
+<img><t:attr name="src"><t:slot name="profile_image_url" /></t:attr></img>
+<p><t:slot name="person_name" /></p>
+</div>
diff --git a/doc/web/howto/listings/slots-attributes-output.html b/doc/web/howto/listings/slots-attributes-output.html
new file mode 100644
index 0000000..42b9c4e
--- /dev/null
+++ b/doc/web/howto/listings/slots-attributes-output.html
@@ -0,0 +1,4 @@
+<div class="profile">
+<img src="http://example.com/user.png" />
+<p>Luke</p>
+</div>
diff --git a/doc/web/howto/listings/slots_attributes_1.py b/doc/web/howto/listings/slots_attributes_1.py
new file mode 100644
index 0000000..0de0c0b
--- /dev/null
+++ b/doc/web/howto/listings/slots_attributes_1.py
@@ -0,0 +1,12 @@
+from twisted.web.template import Element, renderer, XMLFile
+from twisted.python.filepath import FilePath
+
+class ExampleElement(Element):
+ loader = XMLFile(FilePath('slots-attributes-1.xml'))
+
+ @renderer
+ def person_profile(self, request, tag):
+ # Note how convenient it is to pass these attributes in!
+ tag.fillSlots(person_name='Luke',
+ profile_image_url='http://example.com/user.png')
+ return tag
diff --git a/doc/web/howto/listings/soap.rpy b/doc/web/howto/listings/soap.rpy
new file mode 100644
index 0000000..957380d
--- /dev/null
+++ b/doc/web/howto/listings/soap.rpy
@@ -0,0 +1,13 @@
+from twisted.web import soap
+import os
+
+def getQuote():
+ return "That beverage, sir, is off the hizzy."
+
+class Quoter(soap.SOAPPublisher):
+ """Publish one method, 'quote'."""
+
+ def soap_quote(self):
+ return getQuote()
+
+resource = Quoter()
diff --git a/doc/web/howto/listings/subviews-1.py b/doc/web/howto/listings/subviews-1.py
new file mode 100644
index 0000000..55538ec
--- /dev/null
+++ b/doc/web/howto/listings/subviews-1.py
@@ -0,0 +1,27 @@
+from twisted.web.template import (
+ XMLFile, TagLoader, Element, renderer, flattenString)
+from twisted.python.filepath import FilePath
+
+class WidgetsElement(Element):
+ loader = XMLFile(FilePath('subviews-1.xml'))
+
+ widgetData = ['gadget', 'contraption', 'gizmo', 'doohickey']
+
+ @renderer
+ def widgets(self, request, tag):
+ for widget in self.widgetData:
+ yield WidgetElement(TagLoader(tag), widget)
+
+class WidgetElement(Element):
+ def __init__(self, loader, name):
+ Element.__init__(self, loader)
+ self._name = name
+
+ @renderer
+ def name(self, request, tag):
+ return tag(self._name)
+
+def printResult(result):
+ print result
+
+flattenString(None, WidgetsElement()).addCallback(printResult)
diff --git a/doc/web/howto/listings/subviews-1.xml b/doc/web/howto/listings/subviews-1.xml
new file mode 100644
index 0000000..f0eb425
--- /dev/null
+++ b/doc/web/howto/listings/subviews-1.xml
@@ -0,0 +1,3 @@
+<ul xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
+ <li t:render="widgets"><span t:render="name" /></li>
+</ul>
diff --git a/doc/web/howto/listings/subviews-output-1.xml b/doc/web/howto/listings/subviews-output-1.xml
new file mode 100644
index 0000000..3d4265a
--- /dev/null
+++ b/doc/web/howto/listings/subviews-output-1.xml
@@ -0,0 +1,3 @@
+<ul>
+ <li><span>gadget</span></li><li><span>contraption</span></li><li><span>gizmo</span></li><li><span>doohickey</span></li>
+</ul>
diff --git a/doc/web/howto/listings/template-1.xml b/doc/web/howto/listings/template-1.xml
new file mode 100644
index 0000000..7971375
--- /dev/null
+++ b/doc/web/howto/listings/template-1.xml
@@ -0,0 +1,9 @@
+<html xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
+<body>
+ <div t:render="header" />
+ <div id="content">
+ <p>Content goes here.</p>
+ </div>
+ <div t:render="footer" />
+</body>
+</html>
diff --git a/doc/web/howto/listings/transparent-1.xml b/doc/web/howto/listings/transparent-1.xml
new file mode 100644
index 0000000..b45c603
--- /dev/null
+++ b/doc/web/howto/listings/transparent-1.xml
@@ -0,0 +1,6 @@
+<div xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
+<!-- layout decision - these things need to be *siblings* -->
+<t:transparent t:render="renderer1" />
+<t:transparent t:render="renderer2" />
+</div>
+
diff --git a/doc/web/howto/listings/transparent-output.html b/doc/web/howto/listings/transparent-output.html
new file mode 100644
index 0000000..e0ec7e7
--- /dev/null
+++ b/doc/web/howto/listings/transparent-output.html
@@ -0,0 +1,5 @@
+<div>
+<!-- layout decision - these things need to be *siblings* -->
+hello
+world
+</div>
diff --git a/doc/web/howto/listings/transparent_element.py b/doc/web/howto/listings/transparent_element.py
new file mode 100644
index 0000000..bc788dd
--- /dev/null
+++ b/doc/web/howto/listings/transparent_element.py
@@ -0,0 +1,13 @@
+from twisted.web.template import Element, renderer, XMLFile
+from twisted.python.filepath import FilePath
+
+class ExampleElement(Element):
+ loader = XMLFile(FilePath('transparent-1.xml'))
+
+ @renderer
+ def renderer1(self, request, tag):
+ return tag("hello")
+
+ @renderer
+ def renderer2(self, request, tag):
+ return tag("world")
diff --git a/doc/web/howto/listings/wait_for_it.py b/doc/web/howto/listings/wait_for_it.py
new file mode 100644
index 0000000..b6fae92
--- /dev/null
+++ b/doc/web/howto/listings/wait_for_it.py
@@ -0,0 +1,32 @@
+import sys
+from twisted.web.template import XMLString, Element, renderer, flatten
+from twisted.internet.defer import Deferred
+
+sample = XMLString(
+ """
+ <div xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
+ Before waiting ...
+ <span t:render="wait"></span>
+ ... after waiting.
+ </div>
+ """)
+
+class WaitForIt(Element):
+ def __init__(self):
+ Element.__init__(self, loader=sample)
+ self.deferred = Deferred()
+
+ @renderer
+ def wait(self, request, tag):
+ return self.deferred.addCallback(
+ lambda aValue: tag("A value: " + repr(aValue)))
+
+def done(ignore):
+ print("[[[Deferred fired.]]]")
+
+print('[[[Rendering the template.]]]')
+it = WaitForIt()
+flatten(None, it, sys.stdout.write).addCallback(done)
+print('[[[In progress... now firing the Deferred.]]]')
+it.deferred.callback("<value>")
+print('[[[All done.]]]')
diff --git a/doc/web/howto/listings/waited-for-it.html b/doc/web/howto/listings/waited-for-it.html
new file mode 100644
index 0000000..36b62b5
--- /dev/null
+++ b/doc/web/howto/listings/waited-for-it.html
@@ -0,0 +1,8 @@
+[[[Rendering the template.]]]
+<div>
+ Before waiting ...
+ [[[In progress... now firing the Deferred.]]]
+<span>A value: '&lt;value&gt;'</span>
+ ... after waiting.
+ </div>[[[Deferred fired.]]]
+[[[All done.]]]
diff --git a/doc/web/howto/listings/waited-for-it.txt b/doc/web/howto/listings/waited-for-it.txt
new file mode 100644
index 0000000..36b62b5
--- /dev/null
+++ b/doc/web/howto/listings/waited-for-it.txt
@@ -0,0 +1,8 @@
+[[[Rendering the template.]]]
+<div>
+ Before waiting ...
+ [[[In progress... now firing the Deferred.]]]
+<span>A value: '&lt;value&gt;'</span>
+ ... after waiting.
+ </div>[[[Deferred fired.]]]
+[[[All done.]]]
diff --git a/doc/web/howto/listings/webquote.rtl b/doc/web/howto/listings/webquote.rtl
new file mode 100644
index 0000000..8807ac4
--- /dev/null
+++ b/doc/web/howto/listings/webquote.rtl
@@ -0,0 +1,20 @@
+from twisted.web.resource import Resource
+
+def getQuote():
+ return "An apple a day keeps the doctor away."
+
+
+class QuoteResource(Resource):
+
+ template render(self, request):
+ """\
+ <html>
+ <head><title>Quotes Galore</title></head>
+
+ <body><h1>Quotes</h1>"""
+ getQuote()
+ "</body></html>"
+
+
+resource = QuoteResource()
+
diff --git a/doc/web/howto/listings/xmlAndSoapQuote.py b/doc/web/howto/listings/xmlAndSoapQuote.py
new file mode 100644
index 0000000..f17eb28
--- /dev/null
+++ b/doc/web/howto/listings/xmlAndSoapQuote.py
@@ -0,0 +1,25 @@
+from twisted.web import soap, xmlrpc, resource, server
+import os
+
+def getQuote():
+ return "Victory to the burgeois, you capitalist swine!"
+
+class XMLRPCQuoter(xmlrpc.XMLRPC):
+ def xmlrpc_quote(self):
+ return getQuote()
+
+class SOAPQuoter(soap.SOAPPublisher):
+ def soap_quote(self):
+ return getQuote()
+
+def main():
+ from twisted.internet import reactor
+ root = resource.Resource()
+ root.putChild('RPC2', XMLRPCQuoter())
+ root.putChild('SOAP', SOAPQuoter())
+ reactor.listenTCP(7080, server.Site(root))
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/doc/web/howto/listings/xmlquote.rpy b/doc/web/howto/listings/xmlquote.rpy
new file mode 100644
index 0000000..26b76f0
--- /dev/null
+++ b/doc/web/howto/listings/xmlquote.rpy
@@ -0,0 +1,12 @@
+from twisted.web import xmlrpc
+import os
+
+def getQuote():
+ return "What are you talking about, William?"
+
+class Quoter(xmlrpc.XMLRPC):
+
+ def xmlrpc_quote(self):
+ return getQuote()
+
+resource = Quoter()
diff --git a/doc/web/howto/listings/xmlrpc-customized.py b/doc/web/howto/listings/xmlrpc-customized.py
new file mode 100644
index 0000000..af604eb
--- /dev/null
+++ b/doc/web/howto/listings/xmlrpc-customized.py
@@ -0,0 +1,60 @@
+from twisted.web import xmlrpc, server
+
+class EchoHandler:
+
+ def echo(self, x):
+ """
+ Return all passed args
+ """
+ return x
+
+
+
+class AddHandler:
+
+ def add(self, a, b):
+ """
+ Return sum of arguments.
+ """
+ return a + b
+
+
+
+class Example(xmlrpc.XMLRPC):
+ """
+ An example of using you own policy to fetch the handler
+ """
+
+ def __init__(self):
+ xmlrpc.XMLRPC.__init__(self)
+ self._addHandler = AddHandler()
+ self._echoHandler = EchoHandler()
+
+ #We keep a dict of all relevant
+ #procedure names and callable.
+ self._procedureToCallable = {
+ 'add':self._addHandler.add,
+ 'echo':self._echoHandler.echo
+ }
+
+ def lookupProcedure(self, procedurePath):
+ try:
+ return self._procedureToCallable[procedurePath]
+ except KeyError, e:
+ raise xmlrpc.NoSuchFunction(self.NOT_FOUND,
+ "procedure %s not found" % procedurePath)
+
+ def listProcedures(self):
+ """
+ Since we override lookupProcedure, its suggested to override
+ listProcedures too.
+ """
+ return ['add', 'echo']
+
+
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ r = Example()
+ reactor.listenTCP(7080, server.Site(r))
+ reactor.run()
diff --git a/doc/web/howto/resource-templates.html b/doc/web/howto/resource-templates.html
new file mode 100644
index 0000000..d1cb0d2
--- /dev/null
+++ b/doc/web/howto/resource-templates.html
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Light Weight Templating With Resource Templates</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Light Weight Templating With Resource Templates</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Configuring Twisted Web</a></li><li><a href="#auto2">Using ResourceTemplate</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+<h2>Overview<a name="auto0"/></h2>
+
+<p>While high-level templating systems can be used with Twisted (for
+example, <a href="https://launchpad.net/nevow" shape="rect">Divmod
+Nevow</a>, sometimes one needs a less file-heavy system which lets one
+directly write HTML. While
+<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.script.ResourceScript.html" title="twisted.web.script.ResourceScript">ResourceScript</a></code> is
+available, it has a high coding overhead, and requires some boring string
+arithmetic.
+<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.script.ResourceTemplate.html" title="twisted.web.script.ResourceTemplate">ResourceTemplate</a></code> fills the
+space between Nevow and ResourceScript using Quixote's PTL (Python Templating
+Language).</p>
+
+<p>ResourceTemplates need Quixote
+installed. In <a href="http://www.debian.org" shape="rect">Debian</a>, that means
+installing the <code>python-quixote</code> package
+(<code>apt-get install python-quixote</code>). Other operating systems
+require other ways to install Quixote, or it can be done manually.</p>
+
+<h2>Configuring Twisted Web<a name="auto1"/></h2>
+
+<p>The easiest way to get Twisted Web to support ResourceTemplates is to
+bind them to some extension using the web tap's <code>--processor</code>
+flag. Here is an example:</p>
+
+<pre xml:space="preserve">
+% twistd web --path=/var/www \
+ --processor=.rtl=twisted.web.script.ResourceTemplate
+</pre>
+
+<p>The above command line binds the <code>rtl</code> extension to use the
+ResourceTemplate processor. Other ways are possible, but would require
+more Python coding and are outside the scope of this HOWTO.</p>
+
+<h2>Using ResourceTemplate<a name="auto2"/></h2>
+
+<p>ResourceTemplates are coded in an extension of Python called the
+<q>Python Templating Language</q>. Complete documentation of the PTL
+is available
+at <a href="http://quixote.python.ca/quixote.dev/doc/PTL.html" shape="rect">the
+quixote web site</a>. The web server will expect the PTL source file
+to define a variable named <code>resource</code>. This should be
+a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">twisted.web.resource.Resource</a></code>,
+whose <code>.render</code> method be called. Usually, you would want
+to define <code>render</code> using the keyword <code>template</code>
+rather than <code>def</code>.</p>
+
+<p>Here is a simple example for a resource template.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;An apple a day keeps the doctor away.&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QuoteResource</span>(<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">template</span> <span class="py-src-variable">render</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">request</span>):
+ <span class="py-src-string">&quot;&quot;&quot;\
+ &lt;html&gt;
+ &lt;head&gt;&lt;title&gt;Quotes Galore&lt;/title&gt;&lt;/head&gt;
+
+ &lt;body&gt;&lt;h1&gt;Quotes&lt;/h1&gt;&quot;&quot;&quot;</span>
+ <span class="py-src-variable">getQuote</span>()
+ <span class="py-src-string">&quot;&lt;/body&gt;&lt;/html&gt;&quot;</span>
+
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">QuoteResource</span>()
+</pre><div class="caption">Resource Template for Quotes - <a href="listings/webquote.rtl"><span class="filename">listings/webquote.rtl</span></a></div></div>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/twisted-templates.html b/doc/web/howto/twisted-templates.html
new file mode 100644
index 0000000..f998292
--- /dev/null
+++ b/doc/web/howto/twisted-templates.html
@@ -0,0 +1,704 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: HTML Templating with twisted.web.template</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">HTML Templating with twisted.web.template</h1>
+ <div class="toc"><ol><li><a href="#auto0">A Very Quick Introduction To Templating In Python</a></li><li><a href="#auto1">twisted.web.template - Why And How you Might Want to Use It</a></li><ul><li><a href="#auto2">Template Attributes</a></li><li><a href="#auto3">Slots</a></li><li><a href="#auto4">Iteration</a></li><li><a href="#auto5">Sub-views</a></li><li><a href="#auto6">Transparent</a></li></ul><li><a href="#auto7">Quoting</a></li><li><a href="#auto8">Deferreds</a></li><li><a href="#auto9">A Brief Note on Formats and DOCTYPEs</a></li><li><a href="#auto10">A Bit of History</a></li></ol></div>
+ <div class="content">
+<span/>
+<h2>A Very Quick Introduction To Templating In Python<a name="auto0"/></h2>
+<p>
+HTML templating is the process of transforming a template document (one which
+describes style and structure, but does not itself include any content) into
+some HTML output which includes information about objects in your application.
+There are many, many libraries for doing this in Python: to name a few, <a href="http://jinja.pocoo.org/" shape="rect">jinja2</a>, <a href="http://docs.djangoproject.com/en/dev/ref/templates/" shape="rect">django templates</a>,
+and <a href="http://www.clearsilver.net/" shape="rect">clearsilver</a>. You can easily use
+any of these libraries in your Twisted Web application, either by running them
+as <a href="web-in-60/wsgi.html" shape="rect">WSGI applications</a> or by calling your
+preferred templating system's APIs to produce their output as strings, and then
+writing those strings to <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.request.Request.write.html" title="twisted.web.request.Request.write">Request.write</a></code>.
+</p>
+<p>Before we begin explaining how to use it, I'd like to stress that you
+don't <i>need</i> to use Twisted's templating system if you prefer some other
+way to generate HTML. Use it if it suits your personal style or your
+application, but feel free to use other things. Twisted includes templating for
+its own use, because the <code>twisted.web</code> server needs to produce HTML
+in various places, and we didn't want to add another large dependency for that.
+Twisted is <em>not</em> in any way incompatible with other systems, so that has
+nothing to do with the fact that we use our own.</p>
+<p>
+</p>
+<h2>twisted.web.template - Why And How you Might Want to Use It<a name="auto1"/></h2>
+<p>
+Twisted includes a templating system, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.html" title="twisted.web.template">twisted.web.template</a></code>. This can be convenient for Twisted
+applications that want to produce some basic HTML for a web interface without an
+additional dependency.
+</p>
+<p>
+<code>twisted.web.template</code> also includes
+support for <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>s, so
+you can incrementally render the output of a page based on the results of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>s that your application
+has returned. This feature is fairly unique among templating libraries.
+</p>
+<p>
+In <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.html" title="twisted.web.template">twisted.web.template</a></code>, templates are XHTML files
+which also contain a special namespace for indicating dynamic portions of the
+document. For example:
+</p>
+<div class="html-listing"><pre class="htmlsource">
+&lt;html xmlns:t=&quot;http://twistedmatrix.com/ns/twisted.web.template/0.1&quot;&gt;
+&lt;body&gt;
+ &lt;div t:render=&quot;header&quot; /&gt;
+ &lt;div id=&quot;content&quot;&gt;
+ &lt;p&gt;Content goes here.&lt;/p&gt;
+ &lt;/div&gt;
+ &lt;div t:render=&quot;footer&quot; /&gt;
+&lt;/body&gt;
+&lt;/html&gt;
+</pre><div class="caption">template example - <a href="listings/template-1.xml"><span class="filename">listings/template-1.xml</span></a></div></div>
+The basic unit of templating is <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.Element.html" title="twisted.web.template.Element">twisted.web.template.Element</a></code>. An Element is given a way of
+loading a bit of markup like the above example, and knows how to
+correlate <code>render</code> attributes within that markup to Python methods
+exposed with <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.renderer.html" title="twisted.web.template.renderer">twisted.web.template.renderer</a></code>:
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">template</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Element</span>, <span class="py-src-variable">renderer</span>, <span class="py-src-variable">XMLFile</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">filepath</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePath</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExampleElement</span>(<span class="py-src-parameter">Element</span>):
+ <span class="py-src-variable">loader</span> = <span class="py-src-variable">XMLFile</span>(<span class="py-src-variable">FilePath</span>(<span class="py-src-string">'template-1.xml'</span>))
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">header</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-string">'Header.'</span>)
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">footer</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-string">'Footer.'</span>)
+</pre><div class="caption">element example - <a href="listings/element_1.py"><span class="filename">listings/element_1.py</span></a></div></div>
+In order to combine the two, we must render the element. For this simple
+example, we can use the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.flattenString.html" title="twisted.web.template.flattenString">flattenString</a></code> API, which will convert a
+single template object - such as an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.Element.html" title="twisted.web.template.Element">Element</a></code> - into a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> which fires with a single string,
+the HTML output of the rendering process.
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">template</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">flattenString</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">element_1</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ExampleElement</span>
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">renderDone</span>(<span class="py-src-parameter">output</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">output</span>
+<span class="py-src-variable">flattenString</span>(<span class="py-src-variable">None</span>, <span class="py-src-variable">ExampleElement</span>()).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">renderDone</span>)
+</pre><div class="caption">rendering snippet - <a href="listings/render_1.py"><span class="filename">listings/render_1.py</span></a></div></div>
+<p>This short program cheats a little bit; we know that there are no <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>s in the template which
+require the reactor to eventually fire; therefore, we can simply add a callback
+which outputs the result. Also, none of the <code>renderer</code> functions
+require the <code>request</code> object, so it's acceptable to
+pass <code>None</code> through here. (The 'request' object here is used only to
+relay information about the rendering process to each renderer, so you may
+always use whatever object makes sense for your application. Note, however,
+that renderers from library code may require an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.iweb.IRequest.html" title="twisted.web.iweb.IRequest">IRequest</a></code>.)</p>
+<p>
+If you run it yourself, you can see that it produces the following output:
+</p>
+<div class="html-listing"><pre class="htmlsource">
+&lt;html&gt;
+&lt;body&gt;
+ &lt;div&gt;Header.&lt;/div&gt;
+ &lt;div id=&quot;content&quot;&gt;
+ &lt;p&gt;Content goes here.&lt;/p&gt;
+ &lt;/div&gt;
+ &lt;div&gt;Footer.&lt;/div&gt;
+&lt;/body&gt;
+&lt;/html&gt;
+</pre><div class="caption">rendering output 1 - <a href="listings/output-1.html"><span class="filename">listings/output-1.html</span></a></div></div>
+The third parameter to a renderer method is a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.Tag.html" title="twisted.web.template.Tag">Tag</a></code> object which represents the XML element
+with the <code>t:render</code> attribute in the template. Calling a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.Tag.html" title="twisted.web.template.Tag">Tag</a></code> adds children to the element
+in the DOM, which may be strings, more <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.Tag.html" title="twisted.web.template.Tag">Tag</a></code>s, or other renderables such as <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.Element.html" title="twisted.web.template.Element">Element</a></code>s.
+For example, to make the header and footer bold:
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">template</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Element</span>, <span class="py-src-variable">renderer</span>, <span class="py-src-variable">XMLFile</span>, <span class="py-src-variable">tags</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">filepath</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePath</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExampleElement</span>(<span class="py-src-parameter">Element</span>):
+ <span class="py-src-variable">loader</span> = <span class="py-src-variable">XMLFile</span>(<span class="py-src-variable">FilePath</span>(<span class="py-src-string">'template-1.xml'</span>))
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">header</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-variable">tags</span>.<span class="py-src-variable">b</span>(<span class="py-src-string">'Header.'</span>))
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">footer</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-variable">tags</span>.<span class="py-src-variable">b</span>(<span class="py-src-string">'Footer.'</span>))
+</pre><div class="caption">tag manipulation example - <a href="listings/element_2.py"><span class="filename">listings/element_2.py</span></a></div></div>
+
+Rendering this in a similar way to the first example would produce:
+
+<div class="html-listing"><pre class="htmlsource">
+&lt;html&gt;
+&lt;body&gt;
+ &lt;div&gt;&lt;b&gt;Header.&lt;/b&gt;&lt;/div&gt;
+ &lt;div id=&quot;content&quot;&gt;
+ &lt;p&gt;Content goes here.&lt;/p&gt;
+ &lt;/div&gt;
+ &lt;div&gt;&lt;b&gt;Footer.&lt;/b&gt;&lt;/div&gt;
+&lt;/body&gt;
+&lt;/html&gt;
+</pre><div class="caption">tag manipulation output - <a href="listings/output-2.html"><span class="filename">listings/output-2.html</span></a></div></div>
+
+In addition to adding children, call syntax can be used to set attributes on a
+tag. For example, to change the <code>id</code> on the <code>div</code> while
+adding children:
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">template</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Element</span>, <span class="py-src-variable">renderer</span>, <span class="py-src-variable">XMLFile</span>, <span class="py-src-variable">tags</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">filepath</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePath</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExampleElement</span>(<span class="py-src-parameter">Element</span>):
+ <span class="py-src-variable">loader</span> = <span class="py-src-variable">XMLFile</span>(<span class="py-src-variable">FilePath</span>(<span class="py-src-string">'template-1.xml'</span>))
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">header</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-variable">tags</span>.<span class="py-src-variable">p</span>(<span class="py-src-string">'Header.'</span>), <span class="py-src-variable">id</span>=<span class="py-src-string">'header'</span>)
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">footer</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-variable">tags</span>.<span class="py-src-variable">p</span>(<span class="py-src-string">'Footer.'</span>), <span class="py-src-variable">id</span>=<span class="py-src-string">'footer'</span>)
+</pre><div class="caption">attributes example - <a href="listings/element_3.py"><span class="filename">listings/element_3.py</span></a></div></div>
+
+And this would produce the following page:
+
+<div class="html-listing"><pre class="htmlsource">
+&lt;html&gt;
+&lt;body&gt;
+ &lt;div id=&quot;header&quot;&gt;&lt;p&gt;Header.&lt;/p&gt;&lt;/div&gt;
+ &lt;div id=&quot;content&quot;&gt;
+ &lt;p&gt;Content goes here.&lt;/p&gt;
+ &lt;/div&gt;
+ &lt;div id=&quot;footer&quot;&gt;&lt;p&gt;Footer.&lt;/p&gt;&lt;/div&gt;
+&lt;/body&gt;
+&lt;/html&gt;
+</pre><div class="caption">attributes output - <a href="listings/output-3.html"><span class="filename">listings/output-3.html</span></a></div></div>
+
+<p>
+Calling a tag mutates it, it and returns the tag itself, so you can pass it
+forward and call it multiple times if you have multiple children or attributes
+to add to it. <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.html" title="twisted.web.template">twisted.web.template</a></code> also exposes some
+convenient objects for building more complex markup structures from within
+renderer methods in the <code>tags</code> object. In the examples above, we've
+only used <code>tags.p</code> and <code>tags.b</code>, but there should be a <code>tags.x</code> for each <em>x</em> which is a valid HTML tag. There may be
+some omissions, but if you find one, please feel free to file a bug.
+</p>
+
+<h3>Template Attributes<a name="auto2"/></h3>
+
+<code>t:attr</code> tags allow you to set HTML attributes
+(like <code>href</code> in an <code>&lt;a href=&quot;...</code>) on an enclosing
+element.
+
+<h3>Slots<a name="auto3"/></h3>
+
+<code>t:slot</code> tags allow you to specify &quot;slots&quot; which you can
+conveniently fill with multiple pieces of data straight from your Python
+program.
+
+The following example demonstrates both <code>t:attr</code>
+and <code>t:slot</code> in action. Here we have a layout which displays a person's
+profile on your snazzy new Twisted-powered social networking site. We use
+the <code>t:attr</code> tag to drop in the &quot;src&quot; attribute on the profile picture,
+where the actual value of src attribute gets specified by a <code>t:slot</code>
+tag <em>within</em> the <code>t:attr</code> tag. Confused? It should make more
+sense when you see the code:
+
+<div class="html-listing"><pre class="htmlsource">
+&lt;div xmlns:t=&quot;http://twistedmatrix.com/ns/twisted.web.template/0.1&quot;
+ t:render=&quot;person_profile&quot;
+ class=&quot;profile&quot;&gt;
+&lt;img&gt;&lt;t:attr name=&quot;src&quot;&gt;&lt;t:slot name=&quot;profile_image_url&quot; /&gt;&lt;/t:attr&gt;&lt;/img&gt;
+&lt;p&gt;&lt;t:slot name=&quot;person_name&quot; /&gt;&lt;/p&gt;
+&lt;/div&gt;
+</pre><div class="caption">slots and attributes template - <a href="listings/slots-attributes-1.xml"><span class="filename">listings/slots-attributes-1.xml</span></a></div></div>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">template</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Element</span>, <span class="py-src-variable">renderer</span>, <span class="py-src-variable">XMLFile</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">filepath</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePath</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExampleElement</span>(<span class="py-src-parameter">Element</span>):
+ <span class="py-src-variable">loader</span> = <span class="py-src-variable">XMLFile</span>(<span class="py-src-variable">FilePath</span>(<span class="py-src-string">'slots-attributes-1.xml'</span>))
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">person_profile</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-comment"># Note how convenient it is to pass these attributes in!</span>
+ <span class="py-src-variable">tag</span>.<span class="py-src-variable">fillSlots</span>(<span class="py-src-variable">person_name</span>=<span class="py-src-string">'Luke'</span>,
+ <span class="py-src-variable">profile_image_url</span>=<span class="py-src-string">'http://example.com/user.png'</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>
+</pre><div class="caption">slots and attributes element - <a href="listings/slots_attributes_1.py"><span class="filename">listings/slots_attributes_1.py</span></a></div></div>
+<div class="html-listing"><pre class="htmlsource">
+&lt;div class=&quot;profile&quot;&gt;
+&lt;img src=&quot;http://example.com/user.png&quot; /&gt;
+&lt;p&gt;Luke&lt;/p&gt;
+&lt;/div&gt;
+</pre><div class="caption">slots and attributes output - <a href="listings/slots-attributes-output.html"><span class="filename">listings/slots-attributes-output.html</span></a></div></div>
+
+<h3>Iteration<a name="auto4"/></h3>
+
+<p>Often, you will have a sequence of things, and want to render each of them,
+repeating a part of the template for each one. This can be done by
+cloning <code>tag</code> in your renderer:</p>
+
+<div class="html-listing"><pre class="htmlsource">
+&lt;ul xmlns:t=&quot;http://twistedmatrix.com/ns/twisted.web.template/0.1&quot;&gt;
+ &lt;li t:render=&quot;widgets&quot;&gt;&lt;t:slot name=&quot;widgetName&quot;/&gt;&lt;/li&gt;
+&lt;/ul&gt;
+</pre><div class="caption">iteration template - <a href="listings/iteration-1.xml"><span class="filename">listings/iteration-1.xml</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">template</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Element</span>, <span class="py-src-variable">renderer</span>, <span class="py-src-variable">XMLFile</span>, <span class="py-src-variable">flattenString</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">filepath</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePath</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">WidgetsElement</span>(<span class="py-src-parameter">Element</span>):
+ <span class="py-src-variable">loader</span> = <span class="py-src-variable">XMLFile</span>(<span class="py-src-variable">FilePath</span>(<span class="py-src-string">'iteration-1.xml'</span>))
+
+ <span class="py-src-variable">widgetData</span> = [<span class="py-src-string">'gadget'</span>, <span class="py-src-string">'contraption'</span>, <span class="py-src-string">'gizmo'</span>, <span class="py-src-string">'doohickey'</span>]
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">widgets</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">widget</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">widgetData</span>:
+ <span class="py-src-keyword">yield</span> <span class="py-src-variable">tag</span>.<span class="py-src-variable">clone</span>().<span class="py-src-variable">fillSlots</span>(<span class="py-src-variable">widgetName</span>=<span class="py-src-variable">widget</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">result</span>
+
+<span class="py-src-variable">flattenString</span>(<span class="py-src-variable">None</span>, <span class="py-src-variable">WidgetsElement</span>()).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+</pre><div class="caption">iteration element - <a href="listings/iteration-1.py"><span class="filename">listings/iteration-1.py</span></a></div></div>
+<div class="html-listing"><pre class="htmlsource">
+&lt;ul&gt;
+ &lt;li&gt;gadget&lt;/li&gt;&lt;li&gt;contraption&lt;/li&gt;&lt;li&gt;gizmo&lt;/li&gt;&lt;li&gt;doohickey&lt;/li&gt;
+&lt;/ul&gt;
+</pre><div class="caption">iteration output - <a href="listings/iteration-output-1.xml"><span class="filename">listings/iteration-output-1.xml</span></a></div></div>
+
+<p>This renderer works because a renderer can return anything that can be
+rendered, not just <code>tag</code>. In this case, we define a generator, which
+returns a thing that is iterable. We also could have returned
+a <code>list</code>. Anything that is iterable will be rendered by <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.html" title="twisted.web.template">twisted.web.template</a></code> rendering each item in it. In
+this case, each item is a copy of the tag the renderer received, each filled
+with the name of a widget.</p>
+
+<h3>Sub-views<a name="auto5"/></h3>
+
+<p>Another common pattern is to delegate the rendering logic for a small part of
+the page to a separate <code>Element</code>. For example, the widgets from the
+iteration example above might be more complicated to render. You can define
+an <code>Element</code> subclass which can render a single widget. The renderer
+method on the container can then yield instances of this
+new <code>Element</code> subclass.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+</p>&lt;<span class="py-src-variable">ul</span> <span class="py-src-variable">xmlns</span>:<span class="py-src-variable">t</span>=<span class="py-src-string">&quot;http://twistedmatrix.com/ns/twisted.web.template/0.1&quot;</span>&gt;
+ &lt;<span class="py-src-variable">li</span> <span class="py-src-variable">t</span>:<span class="py-src-variable">render</span>=<span class="py-src-string">&quot;widgets&quot;</span>&gt;&lt;<span class="py-src-variable">span</span> <span class="py-src-variable">t</span>:<span class="py-src-variable">render</span>=<span class="py-src-string">&quot;name&quot;</span> /&gt;&lt;/<span class="py-src-variable">li</span>&gt;
+&lt;/<span class="py-src-variable">ul</span>&gt;
+</pre><div class="caption">subview template - <a href="listings/subviews-1.xml"><span class="filename">listings/subviews-1.xml</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">template</span> <span class="py-src-keyword">import</span> (
+ <span class="py-src-variable">XMLFile</span>, <span class="py-src-variable">TagLoader</span>, <span class="py-src-variable">Element</span>, <span class="py-src-variable">renderer</span>, <span class="py-src-variable">flattenString</span>)
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">filepath</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePath</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">WidgetsElement</span>(<span class="py-src-parameter">Element</span>):
+ <span class="py-src-variable">loader</span> = <span class="py-src-variable">XMLFile</span>(<span class="py-src-variable">FilePath</span>(<span class="py-src-string">'subviews-1.xml'</span>))
+
+ <span class="py-src-variable">widgetData</span> = [<span class="py-src-string">'gadget'</span>, <span class="py-src-string">'contraption'</span>, <span class="py-src-string">'gizmo'</span>, <span class="py-src-string">'doohickey'</span>]
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">widgets</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">widget</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">widgetData</span>:
+ <span class="py-src-keyword">yield</span> <span class="py-src-variable">WidgetElement</span>(<span class="py-src-variable">TagLoader</span>(<span class="py-src-variable">tag</span>), <span class="py-src-variable">widget</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">WidgetElement</span>(<span class="py-src-parameter">Element</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">loader</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">Element</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">loader</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_name</span> = <span class="py-src-variable">name</span>
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">name</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_name</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">result</span>
+
+<span class="py-src-variable">flattenString</span>(<span class="py-src-variable">None</span>, <span class="py-src-variable">WidgetsElement</span>()).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+</pre><div class="caption">subview element - <a href="listings/subviews-1.py"><span class="filename">listings/subviews-1.py</span></a></div></div>
+<div class="html-listing"><pre class="htmlsource">
+&lt;ul&gt;
+ &lt;li&gt;&lt;span&gt;gadget&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;contraption&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;gizmo&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;doohickey&lt;/span&gt;&lt;/li&gt;
+&lt;/ul&gt;
+</pre><div class="caption">subview output - <a href="listings/subviews-output-1.xml"><span class="filename">listings/subviews-output-1.xml</span></a></div></div>
+
+<p><code>TagLoader</code> lets the portion of the overall template related to
+widgets be re-used for <code>WidgetElement</code>, which is otherwise a
+normal <code>Element</code> subclass not much different
+from <code>WidgetsElement</code>. Notice that the <em>name</em> renderer on
+the <code>span</code> tag in this template is satisfied
+from <code>WidgetElement</code>, not <code>WidgetsElement</code>.</p>
+
+<h3>Transparent<a name="auto6"/></h3>
+
+Note how renderers, slots and attributes require you to specify a renderer on
+some outer HTML element. What if you don't want to be forced to add an element
+to your DOM just to drop some content into it? Maybe it messes with your
+layout, and you can't get it to work in IE with that extra <code>div</code>
+tag? Perhaps you need <code>t:transparent</code>, which allows you to drop some
+content in without any surrounding &quot;container&quot; tag. For example:
+
+<div class="html-listing"><pre class="htmlsource">
+&lt;div xmlns:t=&quot;http://twistedmatrix.com/ns/twisted.web.template/0.1&quot;&gt;
+&lt;!-- layout decision - these things need to be *siblings* --&gt;
+&lt;t:transparent t:render=&quot;renderer1&quot; /&gt;
+&lt;t:transparent t:render=&quot;renderer2&quot; /&gt;
+&lt;/div&gt;
+
+</pre><div class="caption">transparent template - <a href="listings/transparent-1.xml"><span class="filename">listings/transparent-1.xml</span></a></div></div>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">template</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Element</span>, <span class="py-src-variable">renderer</span>, <span class="py-src-variable">XMLFile</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">filepath</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePath</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExampleElement</span>(<span class="py-src-parameter">Element</span>):
+ <span class="py-src-variable">loader</span> = <span class="py-src-variable">XMLFile</span>(<span class="py-src-variable">FilePath</span>(<span class="py-src-string">'transparent-1.xml'</span>))
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">renderer1</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-string">&quot;hello&quot;</span>)
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">renderer2</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-string">&quot;world&quot;</span>)
+</pre><div class="caption">transparent element - <a href="listings/transparent_element.py"><span class="filename">listings/transparent_element.py</span></a></div></div>
+
+<div class="html-listing"><pre class="htmlsource">
+&lt;div&gt;
+&lt;!-- layout decision - these things need to be *siblings* --&gt;
+hello
+world
+&lt;/div&gt;
+</pre><div class="caption">transparent rendering output - <a href="listings/transparent-output.html"><span class="filename">listings/transparent-output.html</span></a></div></div>
+
+<h2>Quoting<a name="auto7"/></h2>
+
+<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.html" title="twisted.web.template">twisted.web.template</a></code> will quote any strings that place
+into the DOM. This provides protection against <a href="http://en.wikipedia.org/wiki/Cross-site_scripting" shape="rect">XSS attacks</a>, in
+addition to just generally making it easy to put arbitrary strings onto a web
+page, without worrying about what they might have in them. This can easily be
+demonstrated with an element using the same template from our earlier examples.
+Here's an element that returns some &quot;special&quot; characters in HTML ('&lt;', '&gt;',
+and '&quot;', which is special in attribute values):
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">template</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Element</span>, <span class="py-src-variable">renderer</span>, <span class="py-src-variable">XMLFile</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">filepath</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePath</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExampleElement</span>(<span class="py-src-parameter">Element</span>):
+ <span class="py-src-variable">loader</span> = <span class="py-src-variable">XMLFile</span>(<span class="py-src-variable">FilePath</span>(<span class="py-src-string">'template-1.xml'</span>))
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">header</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-string">'&lt;&lt;&lt;Header&gt;&gt;&gt;!'</span>)
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">footer</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">tag</span>(<span class="py-src-string">'&gt;&gt;&gt;&quot;Footer!&quot;&lt;&lt;&lt;'</span>, <span class="py-src-variable">id</span>=<span class="py-src-string">'&lt;&quot;fun&quot;&gt;'</span>)
+</pre><div class="caption">renderers returning &quot;special&quot; characters - <a href="listings/quoting_element.py"><span class="filename">listings/quoting_element.py</span></a></div></div>
+
+Note that they are all safely quoted in the output, and will appear in a web
+browser just as you returned them from your Python method:
+
+<div class="html-listing"><pre class="htmlsource">
+&lt;html&gt;
+&lt;body&gt;
+ &lt;div&gt;&amp;lt;&amp;lt;&amp;lt;Header&amp;gt;&amp;gt;&amp;gt;!&lt;/div&gt;
+ &lt;div id=&quot;content&quot;&gt;
+ &lt;p&gt;Content goes here.&lt;/p&gt;
+ &lt;/div&gt;
+ &lt;div id=&quot;&amp;lt;&amp;quot;fun&amp;quot;&amp;gt;&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&quot;Footer!&quot;&amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
+&lt;/body&gt;
+&lt;/html&gt;
+</pre><div class="caption">output containing &quot;special&quot; characters - <a href="listings/quoting-output.html"><span class="filename">listings/quoting-output.html</span></a></div></div>
+
+<h2>Deferreds<a name="auto8"/></h2>
+
+Finally, a simple demonstration of Deferred support, the unique feature of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.html" title="twisted.web.template">twisted.web.template</a></code>. Simply put, any renderer may
+return a Deferred which fires with some template content instead of the template
+content itself. As shown above, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.flattenString.html" title="twisted.web.template.flattenString">flattenString</a></code> will return a Deferred that
+fires with the full content of the string. But if there's a lot of content, you
+might not want to wait before starting to send some of it to your HTTP client:
+for that case, you can use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.flatten.html" title="twisted.web.template.flatten">flatten</a></code>.
+It's difficult to demonstrate this directly in a browser-based application;
+unless you insert very long delays before firing your Deferreds, it just looks
+like your browser is instantly displaying everything. Here's an example that
+just prints out some HTML template, with markers inserted for where certain
+events happen:
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">template</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">XMLString</span>, <span class="py-src-variable">Element</span>, <span class="py-src-variable">renderer</span>, <span class="py-src-variable">flatten</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Deferred</span>
+
+<span class="py-src-variable">sample</span> = <span class="py-src-variable">XMLString</span>(
+ <span class="py-src-string">&quot;&quot;&quot;
+ &lt;div xmlns:t=&quot;http://twistedmatrix.com/ns/twisted.web.template/0.1&quot;&gt;
+ Before waiting ...
+ &lt;span t:render=&quot;wait&quot;&gt;&lt;/span&gt;
+ ... after waiting.
+ &lt;/div&gt;
+ &quot;&quot;&quot;</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">WaitForIt</span>(<span class="py-src-parameter">Element</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">Element</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">loader</span>=<span class="py-src-variable">sample</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">deferred</span> = <span class="py-src-variable">Deferred</span>()
+
+ @<span class="py-src-variable">renderer</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">wait</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">tag</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(
+ <span class="py-src-keyword">lambda</span> <span class="py-src-variable">aValue</span>: <span class="py-src-variable">tag</span>(<span class="py-src-string">&quot;A value: &quot;</span> + <span class="py-src-variable">repr</span>(<span class="py-src-variable">aValue</span>)))
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">done</span>(<span class="py-src-parameter">ignore</span>):
+ <span class="py-src-keyword">print</span>(<span class="py-src-string">&quot;[[[Deferred fired.]]]&quot;</span>)
+
+<span class="py-src-keyword">print</span>(<span class="py-src-string">'[[[Rendering the template.]]]'</span>)
+<span class="py-src-variable">it</span> = <span class="py-src-variable">WaitForIt</span>()
+<span class="py-src-variable">flatten</span>(<span class="py-src-variable">None</span>, <span class="py-src-variable">it</span>, <span class="py-src-variable">sys</span>.<span class="py-src-variable">stdout</span>.<span class="py-src-variable">write</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">done</span>)
+<span class="py-src-keyword">print</span>(<span class="py-src-string">'[[[In progress... now firing the Deferred.]]]'</span>)
+<span class="py-src-variable">it</span>.<span class="py-src-variable">deferred</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;&lt;value&gt;&quot;</span>)
+<span class="py-src-keyword">print</span>(<span class="py-src-string">'[[[All done.]]]'</span>)
+</pre><div class="caption">deferred example - <a href="listings/wait_for_it.py"><span class="filename">listings/wait_for_it.py</span></a></div></div>
+
+If you run this example, you should get the following output:
+
+<div class="html-listing"><pre class="htmlsource">
+[[[Rendering the template.]]]
+&lt;div&gt;
+ Before waiting ...
+ [[[In progress... now firing the Deferred.]]]
+&lt;span&gt;A value: '&amp;lt;value&amp;gt;'&lt;/span&gt;
+ ... after waiting.
+ &lt;/div&gt;[[[Deferred fired.]]]
+[[[All done.]]]
+</pre><div class="caption">output from deferred example - <a href="listings/waited-for-it.html"><span class="filename">listings/waited-for-it.html</span></a></div></div>
+
+This demonstrates that part of the output (everything up to
+&quot;<code>[[[In progress...</code>&quot;) is written out immediately as it's rendered.
+But once it hits the Deferred, <code>WaitForIt</code>'s rendering needs to pause
+until <code>.callback(...)</code> is called on that Deferred. You can see that
+no further output is produced until the message indicating that the Deferred is
+being fired is complete. By returning Deferreds and using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.template.flatten.html" title="twisted.web.template.flatten">flatten</a></code>, you can avoid buffering large
+amounts of data.
+
+<h2>A Brief Note on Formats and DOCTYPEs<a name="auto9"/></h2>
+
+<p>
+The goal of <code>twisted.web.template</code> is to emit both valid <a href="http://whatwg.org/html" shape="rect">HTML</a> or <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-xhtml-syntax.html#the-xhtml-syntax" shape="rect">XHTML</a>.
+However, in order to get the maximally standards-compliant output format you
+desire, you have to know which one you want, and take a few simple steps to emit
+it correctly. Many browsers will probably work with most output if you ignore
+this section entirely, but <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#the-doctype" shape="rect">the
+ HTML specification recommends that you specify an appropriate DOCTYPE</a>.
+</p>
+
+<p>
+As a <code>DOCTYPE</code> declaration in your template would describe the
+template itself, rather than its output, it won't be included in your output.
+If you wish to annotate your template output with a DOCTYPE, you will have to
+write it to the browser out of band. One way to do this would be to simply
+do <code>request.write('&lt;!DOCTYPE html&gt;\n')</code> when you are ready to
+begin emitting your response. The same goes for an XML <code>DOCTYPE</code>
+declaration.
+</p>
+
+<p>
+<code>twisted.web.template</code> will remove the <code>xmlns</code> attributes
+used to declare
+the <code>http://twistedmatrix.com/ns/twisted.web.template/0.1</code> namespace,
+but it will not modify other namespace declaration attributes. Therefore if you
+wish to serialize in HTML format, you should not use other namespaces; if you
+wish to serialize to XML, feel free to insert any namespace declarations that
+are appropriate, and they will appear in your output.
+</p>
+
+<div class="note"><strong>Note: </strong>
+This relaxed approach is correct in many cases. However, in certain contexts -
+especially &lt;script&gt; and &lt;style&gt; tags - quoting rules differ in
+significant ways between HTML and XML, and between different browsers' parsers
+in HTML. If you want to generate dynamic content inside a script or stylesheet,
+the best option is to load the resource externally so you don't have to worry
+about quoting rules. The second best option is to strictly configure your
+content-types and DOCTYPE declarations for XML, whose quoting rules are simple
+and compatible with the approach that <code>twisted.web.template</code> takes.
+And, please remember: regardless of how you put it there, any user input placed
+inside a &lt;script&gt; or &lt;style&gt; tag is a potential security issue.
+</div>
+
+<h2>A Bit of History<a name="auto10"/></h2>
+<p>
+Those of you who used Divmod Nevow may notice some
+similarities. <code>twisted.web.template</code> is in fact derived from the
+latest version of Nevow, but includes only the latest components from Nevow's
+rendering pipeline, and does not have any of the legacy compatibility layers
+that Nevow grew over time. This should make
+using <code>twisted.web.template</code> a similar experience for many long-time
+users of Twisted who have previously used Nevow for its twisted-friendly
+templating, but more straightforward for new users.
+</p>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/using-twistedweb.html b/doc/web/howto/using-twistedweb.html
new file mode 100644
index 0000000..c4cfe31
--- /dev/null
+++ b/doc/web/howto/using-twistedweb.html
@@ -0,0 +1,1074 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Configuring and Using the Twisted Web Server</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Configuring and Using the Twisted Web Server</h1>
+ <div class="toc"><ol><li><a href="#auto0">Twisted Web Development</a></li><ul><li><a href="#auto1">Main Concepts</a></li><li><a href="#auto2">Site Objects</a></li><li><a href="#auto3">Resource objects</a></li><li><a href="#auto4">Resource Trees</a></li><li><a href="#auto5">.rpy scripts</a></li><li><a href="#auto6">Resource rendering</a></li><li><a href="#auto7">Session</a></li><li><a href="#auto8">Proxies and reverse proxies</a></li></ul><li><a href="#auto9">Advanced Configuration</a></li><ul><li><a href="#auto10">Adding Children</a></li><li><a href="#auto11">Modifying File Resources</a></li><li><a href="#auto12">Virtual Hosts</a></li><li><a href="#auto13">Advanced Techniques</a></li></ul><li><a href="#auto14">Running a Twisted Web Server</a></li><ul><li><a href="#auto15">Serving Flat HTML</a></li><li><a href="#auto16">Resource Scripts</a></li><li><a href="#auto17">Web UIs</a></li><li><a href="#auto18">Spreadable Web Servers</a></li><li><a href="#auto19">Serving PHP/Perl/CGI</a></li><li><a href="#auto20">Serving WSGI Applications</a></li><li><a href="#auto21">Using VHostMonster</a></li></ul><li><a href="#auto22">Rewriting URLs</a></li><li><a href="#auto23">Knowing When We're Not Wanted</a></li><li><a href="#auto24">As-Is Serving</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Twisted Web Development<a name="auto0"/></h2><a name="development" shape="rect"/>
+
+<p>Twisted Web serves Python objects that implement the interface
+IResource.</p>
+
+<br clear="none"/><img alt="Twisted Web process" src="../img/web-process.png"/>
+
+<h3>Main Concepts<a name="auto1"/></h3>
+
+<ul>
+
+<li><a href="#sites" shape="rect">Site Objects</a> are responsible for
+creating <code>HTTPChannel</code> instances to parse the HTTP request,
+and begin the object lookup process. They contain the root Resource,
+the resource which represents the URL <code>/</code> on the site.</li>
+
+<li><a href="#resources" shape="rect">Resource</a> objects represent a single URL segment. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.IResource.html" title="twisted.web.resource.IResource">IResource</a></code> interface describes the methods a Resource object must implement in order to participate in the object publishing process.</li>
+
+<li><a href="#trees" shape="rect">Resource trees</a> are arrangements of Resource objects into a Resource tree. Starting at the root Resource object, the tree of Resource objects defines the URLs which will be valid.</li>
+
+<li><a href="#rpys" shape="rect">.rpy scripts</a> are python scripts which the twisted.web static file server will execute, much like a CGI. However, unlike CGI they must create a Resource object which will be rendered when the URL is visited.</li>
+
+<li><a href="#rendering" shape="rect">Resource rendering</a> occurs when Twisted Web locates a leaf Resource object. A Resource can either return an html string or write to the request object.</li>
+
+<li><a href="#sessions" shape="rect">Session</a> objects allow you to store information across multiple requests. Each individual browser using the system has a unique Session instance.</li>
+
+</ul>
+
+<p>The Twisted Web server is started through the Twisted Daemonizer, as in:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web
+</pre>
+
+<h3>Site Objects<a name="auto2"/></h3>
+<a name="sites" shape="rect"/>
+
+<p>Site objects serve as the glue between a port to listen for HTTP requests on, and a root Resource object.</p>
+
+<p>When using <code>twistd -n web --path /foo/bar/baz</code>, a Site object is created with a root Resource that serves files out of the given path.</p>
+
+<p>You can also create a <code>Site</code> instance by hand, passing
+it a <code>Resource</code> object which will serve as the root of the
+site:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span>, <span class="py-src-variable">resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Simple</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">isLeaf</span> = <span class="py-src-variable">True</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;Hello, world!&lt;/html&gt;&quot;</span>
+
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">Simple</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8080</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h3>Resource objects<a name="auto3"/></h3>
+<a name="resources" shape="rect"/>
+
+<p><code>Resource</code> objects represent a single URL segment of a site. During URL parsing, <code>getChild</code> is called on the current <code>Resource</code> to produce the next <code>Resource</code> object.</p>
+
+<p>When the leaf Resource is reached, either because there were no more URL segments or a Resource had isLeaf set to True, the leaf Resource is rendered by calling <code>render(request)</code>. See <q>Resource Rendering</q> below for more about this.</p>
+
+<p>During the Resource location process, the URL segments which have already been processed and those which have not yet been processed are available in <code>request.prepath</code> and <code>request.postpath</code>.</p>
+
+<p>A Resource can know where it is in the URL tree by looking at <code>request.prepath</code>, a list of URL segment strings.</p>
+
+<p>A Resource can know which path segments will be processed after it by looking at <code>request.postpath</code>.</p>
+
+<p>If the URL ends in a slash, for example <code>http://example.com/foo/bar/</code>, the final URL segment will be an empty string. Resources can thus know if they were requested with or without a final slash.</p>
+
+<p>Here is a simple Resource object:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Hello</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">isLeaf</span> = <span class="py-src-variable">True</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">name</span> == <span class="py-src-string">''</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">Resource</span>.<span class="py-src-variable">getChild</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">name</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Hello, world! I am located at %r.&quot;</span> % (<span class="py-src-variable">request</span>.<span class="py-src-variable">prepath</span>,)
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">Hello</span>()
+</pre>
+
+<h3>Resource Trees<a name="auto4"/></h3>
+<a name="trees" shape="rect"/>
+
+<p>Resources can be arranged in trees using <code>putChild</code>. <code>putChild</code> puts a Resource instance into another Resource instance, making it available at the given path segment name:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-variable">root</span> = <span class="py-src-variable">Hello</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'fred'</span>, <span class="py-src-variable">Hello</span>())
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'bob'</span>, <span class="py-src-variable">Hello</span>())
+</pre>
+
+<p>If this root resource is served as the root of a Site instance, the following URLs will all be valid:</p>
+
+<ul>
+<li><code>http://example.com/</code></li>
+<li><code>http://example.com/fred</code></li>
+<li><code>http://example.com/bob</code></li>
+<li><code>http://example.com/fred/</code></li>
+<li><code>http://example.com/bob/</code></li>
+
+</ul>
+
+<h3>.rpy scripts<a name="auto5"/></h3>
+<a name="rpys" shape="rect"/>
+
+<p>Files with the extension <code>.rpy</code> are python scripts which, when placed in a directory served by Twisted Web, will be executed when visited through the web.</p>
+
+<p>An <code>.rpy</code> script must define a variable, <code>resource</code>, which is the Resource object that will render the request.</p>
+
+<p><code>.rpy</code> files are very convenient for rapid development and prototyping. Since they are executed on every web request, defining a Resource subclass in an <code>.rpy</code> will make viewing the results of changes to your class visible simply by refreshing the page:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;Hello, world!&lt;/html&gt;&quot;</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">MyResource</span>()
+</pre>
+
+<p>However, it is often a better idea to define Resource subclasses in Python modules. In order for changes in modules to be visible, you must either restart the Python process, or reload the module:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">myresource</span>
+
+<span class="py-src-comment">## Comment out this line when finished debugging</span>
+<span class="py-src-variable">reload</span>(<span class="py-src-variable">myresource</span>)
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">myresource</span>.<span class="py-src-variable">MyResource</span>()
+</pre>
+
+<p>Creating a Twisted Web server which serves a directory is easy:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd -n web --path /Users/dsp/Sites
+</pre>
+
+<h3>Resource rendering<a name="auto6"/></h3>
+<a name="rendering" shape="rect"/>
+
+<p>Resource rendering occurs when Twisted Web locates a leaf Resource object to handle a web request. A Resource's <code>render</code> method may do various things to produce output which will be sent back to the browser:</p>
+
+<ul>
+<li>Return a string</li>
+<li>Call <code>request.write(&quot;stuff&quot;)</code> as many times as desired, then call <code>request.finish()</code> and return <code>server.NOT_DONE_YET</code> (This is deceptive, since you are in fact done with the request, but is the correct way to do this)</li>
+
+<li>Request a <code>Deferred</code>, return <code>server.NOT_DONE_YET</code>, and call <code>request.write(&quot;stuff&quot;)</code> and <code>request.finish()</code> later, in a callback on the <code>Deferred</code>.</li>
+</ul>
+
+<p>
+
+The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>
+class, which is usually what one's Resource classes subclass, has a
+convenient default implementation
+of <code class="python">render</code>. It will call a method
+named <code class="python">self.render_METHOD</code>
+where <q>METHOD</q> is whatever HTTP method was used to request this
+resource. Examples: request_GET, request_POST, request_HEAD, and so
+on. It is recommended that you have your resource classes
+subclass <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>
+and implement <code class="python">render_METHOD</code> methods as
+opposed to <code class="python">render</code> itself. Note that for
+certain resources, <code class="python">request_POST =
+request_GET</code> may be desirable in case one wants to process
+arguments passed to the resource regardless of whether they used GET
+(<code>?foo=bar&amp;baz=quux</code>, and so forth) or POST.
+
+</p>
+
+<h3>Session<a name="auto7"/></h3>
+<a name="sessions" shape="rect"/>
+
+<p>HTTP is a stateless protocol; every request-response is treated as an individual unit, distinguishable from any other request only by the URL requested. With the advent of Cookies in the mid nineties, dynamic web servers gained the ability to distinguish between requests coming from different <em>browser sessions</em> by sending a Cookie to a browser. The browser then sends this cookie whenever it makes a request to a web server, allowing the server to track which requests come from which browser session.</p>
+
+<p>Twisted Web provides an abstraction of this browser-tracking behavior called the <em>Session object</em>. Calling <code>request.getSession()</code> checks to see if a session cookie has been set; if not, it creates a unique session id, creates a Session object, stores it in the Site, and returns it. If a session object already exists, the same session object is returned. In this way, you can store data specific to the session in the session object.</p>
+
+<img src="../img/web-session.png"/>
+
+<h3>Proxies and reverse proxies<a name="auto8"/></h3>
+<a name="proxies" shape="rect"/>
+
+<p>A proxy is a general term for a server that functions as an intermediary
+between clients and other servers.</p>
+
+<p>Twisted supports two main proxy variants: a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.Proxy.html" title="twisted.web.proxy.Proxy">Proxy</a></code> and a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.ReverseProxy.html" title="twisted.web.proxy.ReverseProxy">ReverseProxy</a></code>.</p>
+
+<h4>Proxy</h4>
+
+<p>A proxy forwards requests made by a client to a destination server. Proxies
+typically sit on the internal network for a client or out on the internet, and
+have many uses, including caching, packet filtering, auditing, and circumventing
+local access restrictions to web content.</p>
+
+<p>Here is an example of a simple but complete web proxy:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">proxy</span>, <span class="py-src-variable">http</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ProxyFactory</span>(<span class="py-src-parameter">http</span>.<span class="py-src-parameter">HTTPFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">proxy</span>.<span class="py-src-variable">Proxy</span>()
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8080</span>, <span class="py-src-variable">ProxyFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>With this proxy running, you can configure your web browser to use
+<code>localhost:8080</code> as a proxy. After doing so, when browsing the web
+all requests will go through this proxy.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.Proxy.html" title="twisted.web.proxy.Proxy">Proxy</a></code> inherits
+from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.http.HTTPChannel.html" title="twisted.web.http.HTTPChannel">http.HTTPChannel</a></code>. Each client
+request to the proxy generates a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.ProxyRequest.html" title="twisted.web.proxy.ProxyRequest">ProxyRequest</a></code> from the proxy to the destination
+server on behalf of the client. <code>ProxyRequest</code> uses
+a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.ProxyClientFactory.html" title="twisted.web.proxy.ProxyClientFactory">ProxyClientFactory</a></code> to create
+an instance of the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.ProxyClient.html" title="twisted.web.proxy.ProxyClient">ProxyClient</a></code>
+protocol for the connection. <code>ProxyClient</code> inherits
+from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.http.HTTPClient.html" title="twisted.web.http.HTTPClient">http.HTTPClient</a></code>. Subclass <code>ProxyRequest</code> to
+customize the way requests are processed or logged.</p>
+
+<h4>ReverseProxyResource</h4>
+
+<p>A reverse proxy retrieves resources from other servers on behalf of a
+client. Reverse proxies typically sit inside the server's internal network and
+are used for caching, application firewalls, and load balancing.</p>
+
+<p>Here is an example of a basic reverse proxy:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">proxy</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">proxy</span>.<span class="py-src-variable">ReverseProxyResource</span>(<span class="py-src-string">'www.yahoo.com'</span>, <span class="py-src-number">80</span>, <span class="py-src-string">''</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8080</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>With this reverse proxy running locally, you can
+visit <code>http://localhost:8080</code> in your web browser, and the reverse
+proxy will proxy your connection to
+<code>www.yahoo.com</code>.</p>
+
+<p>In this example we use <code base="twisted.web">server.Site</code> to serve
+a <code base="twisted.web.proxy">ReverseProxyResource</code> directly. There is
+also a <code>ReverseProxy</code> family of classes
+in <code>twisted.web.proxy</code> mirroring those of the <code>Proxy</code>
+family:</p>
+
+<p>Like <code>Proxy</code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.ReverseProxy.html" title="twisted.web.proxy.ReverseProxy">ReverseProxy</a></code> inherits
+from <code>http.HTTPChannel</code>. Each client request to the reverse proxy
+generates a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.ReverseProxyRequest.html" title="twisted.web.proxy.ReverseProxyRequest">ReverseProxyRequest</a></code> to the destination
+server. Like <code>ProxyRequest</code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.ReverseProxyRequest.html" title="twisted.web.proxy.ReverseProxyRequest">ReverseProxyRequest</a></code> uses a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.ProxyClientFactory.html" title="twisted.web.proxy.ProxyClientFactory">ProxyClientFactory</a></code> to create an instance of
+the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.proxy.ProxyClient.html" title="twisted.web.proxy.ProxyClient">ProxyClient</a></code> protocol for
+the connection.</p>
+
+<p>Additional examples of proxies and reverse proxies can be found in
+the <a href="../examples/index.html" shape="rect">Twisted web examples</a>.</p>
+
+<h2>Advanced Configuration<a name="auto9"/></h2>
+
+<p>Non-trivial configurations of Twisted Web are achieved with Python
+configuration files. This is a Python snippet which builds up a
+variable called application. Usually,
+a <code>twisted.application.internet.TCPServer</code>
+instance will be used to make the application listen on a TCP port
+(80, in case direct web serving is desired), with the listener being
+a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">twisted.web.server.Site</a></code>. The resulting file
+can then be run with <code class="shell">twistd
+-y</code>. Alternatively a reactor object can be used directly to make
+a runnable script.</p>
+
+<p>The <code>Site</code> will wrap a <code>Resource</code> object -- the
+root.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<p>Most advanced configurations will be in the form of tweaking the
+root resource object.</p>
+
+<h3>Adding Children<a name="auto10"/></h3>
+
+<p>Usually, the root's children will be based on the filesystem's contents.
+It is possible to override the filesystem by explicit <code>putChild</code>
+methods.</p>
+
+<p>Here are two examples. The first one adds a <code>/doc</code> child
+to serve the documentation of the installed packages, while the second
+one adds a <code>cgi-bin</code> directory for CGI scripts.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;doc&quot;</span>, <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/usr/share/doc&quot;</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">twcgi</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;cgi-bin&quot;</span>, <span class="py-src-variable">twcgi</span>.<span class="py-src-variable">CGIDirectory</span>(<span class="py-src-string">&quot;/var/www/cgi-bin&quot;</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h3>Modifying File Resources<a name="auto11"/></h3>
+
+<p><code>File</code> resources, be they root object or children
+thereof, have two important attributes that often need to be
+modified: <code>indexNames</code>
+and <code>processors</code>. <code>indexNames</code> determines which
+files are treated as <q>index files</q> -- served up when a directory
+is rendered. <code>processors</code> determine how certain file
+extensions are treated.</p>
+
+<p>Here is an example for both, creating a site where all <code>.rpy</code>
+extensions are Resource Scripts, and which renders directories by
+searching for a <code>index.rpy</code> file.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">script</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">indexNames</span>=[<span class="py-src-string">'index.rpy'</span>]
+<span class="py-src-variable">root</span>.<span class="py-src-variable">processors</span> = {<span class="py-src-string">'.rpy'</span>: <span class="py-src-variable">script</span>.<span class="py-src-variable">ResourceScript</span>}
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<p><code>File</code> objects also have a method called <code>ignoreExt</code>.
+This method can be used to give extension-less URLs to users, so that
+implementation is hidden. Here is an example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">script</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">ignoreExt</span>(<span class="py-src-string">&quot;.rpy&quot;</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">processors</span> = {<span class="py-src-string">'.rpy'</span>: <span class="py-src-variable">script</span>.<span class="py-src-variable">ResourceScript</span>}
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<p>Now, a URL such as <code>/foo</code> might be served from a Resource
+Script called <code>foo.rpy</code>, if no file by the name of <code>foo</code>
+exists.</p>
+
+<h3>Virtual Hosts<a name="auto12"/></h3>
+
+<p>Virtual hosting is done via a special resource, that should be used
+as the root resource
+-- <code>NameVirtualHost</code>. <code>NameVirtualHost</code> has an
+attribute named <code>default</code>, which holds the default
+website. If a different root for some other name is desired,
+the <code>addHost</code> method should be called.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">vhost</span>, <span class="py-src-variable">script</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">vhost</span>.<span class="py-src-variable">NameVirtualHost</span>()
+
+<span class="py-src-comment"># Add a default -- htdocs</span>
+<span class="py-src-variable">root</span>.<span class="py-src-variable">default</span>=<span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+
+<span class="py-src-comment"># Add a simple virtual host -- foo.com</span>
+<span class="py-src-variable">root</span>.<span class="py-src-variable">addHost</span>(<span class="py-src-string">&quot;foo.com&quot;</span>, <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/foo&quot;</span>))
+
+<span class="py-src-comment"># Add a simple virtual host -- bar.com</span>
+<span class="py-src-variable">root</span>.<span class="py-src-variable">addHost</span>(<span class="py-src-string">&quot;bar.com&quot;</span>, <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/bar&quot;</span>))
+
+<span class="py-src-comment"># The &quot;baz&quot; people want to use Resource Scripts in their web site</span>
+<span class="py-src-variable">baz</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/baz&quot;</span>)
+<span class="py-src-variable">baz</span>.<span class="py-src-variable">processors</span> = {<span class="py-src-string">'.rpy'</span>: <span class="py-src-variable">script</span>.<span class="py-src-variable">ResourceScript</span>}
+<span class="py-src-variable">baz</span>.<span class="py-src-variable">ignoreExt</span>(<span class="py-src-string">'.rpy'</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">addHost</span>(<span class="py-src-string">'baz'</span>, <span class="py-src-variable">baz</span>)
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<h3>Advanced Techniques<a name="auto13"/></h3>
+
+<p>Since the configuration is a Python snippet, it is possible to
+use the full power of Python. Here are some simple examples:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-comment"># No need for configuration of virtual hosts -- just make sure</span>
+<span class="py-src-comment"># a directory /var/vhosts/&lt;vhost name&gt; exists:</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">vhost</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">vhost</span>.<span class="py-src-variable">NameVirtualHost</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">default</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-keyword">for</span> <span class="py-src-variable">dir</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">os</span>.<span class="py-src-variable">listdir</span>(<span class="py-src-string">&quot;/var/vhosts&quot;</span>):
+ <span class="py-src-variable">root</span>.<span class="py-src-variable">addHost</span>(<span class="py-src-variable">dir</span>, <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">join</span>(<span class="py-src-string">&quot;/var/vhosts&quot;</span>, <span class="py-src-variable">dir</span>)))
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-comment"># Determine ports we listen on based on a file with numbers:</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">vhost</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+
+<span class="py-src-keyword">for</span> <span class="py-src-variable">num</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">map</span>(<span class="py-src-variable">int</span>, <span class="py-src-variable">open</span>(<span class="py-src-string">&quot;/etc/web/ports&quot;</span>).<span class="py-src-variable">read</span>().<span class="py-src-variable">split</span>()):
+ <span class="py-src-variable">serviceCollection</span>.<span class="py-src-variable">addCollection</span>(<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-variable">num</span>, <span class="py-src-variable">site</span>))
+</pre>
+
+
+<h2>Running a Twisted Web Server<a name="auto14"/></h2>
+
+<p>In many cases, you'll end up repeating common usage patterns of
+twisted.web. In those cases you'll probably want to use Twisted's
+pre-configured web server setup.</p>
+
+<p>The easiest way to run a Twisted Web server is with the Twisted Daemonizer.
+For example, this command will run a web server which serves static files from
+a particular directory:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web --path /path/to/web/content
+</pre>
+
+<p>If you just want to serve content from your own home directory, the
+following will do:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web --path ~/public_html/
+</pre>
+
+<p>You can stop the server at any time by going back to the directory you
+started it in and running the command:</p>
+
+<pre class="shell" xml:space="preserve">
+% kill `cat twistd.pid`
+</pre>
+
+<p> Some other configuration options are available as well: </p>
+
+<ul>
+ <li> <code>--port</code>: Specify the port for the web
+ server to listen on. This defaults to 8080. </li>
+ <li> <code>--logfile</code>: Specify the path to the
+ log file. </li>
+</ul>
+
+<p> The full set of options that are available can be seen with: </p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web --help
+</pre>
+
+<h3>Serving Flat HTML<a name="auto15"/></h3>
+
+<p> Twisted Web serves flat HTML files just as it does any other flat file. </p>
+
+<a name="ResourceScripts" shape="rect"/>
+<h3>Resource Scripts<a name="auto16"/></h3>
+
+<p> A Resource script is a Python file ending with the extension <code>.rpy</code>, which is required to create an instance of a (subclass of a) <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">twisted.web.resource.Resource</a></code>. </p>
+
+<p> Resource scripts have 3 special variables: </p>
+
+<ul>
+ <li> <code class="py-src-identifier">__file__</code>: The name of the .rpy file, including the full path. This variable is automatically defined and present within the namespace. </li>
+ <li> <code class="py-src-identifier">registry</code>: An object of class <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.static.Registry.html" title="twisted.web.static.Registry">static.Registry</a></code>. It can be used to access and set persistent data keyed by a class.</li>
+ <li> <code class="py-src-identifier">resource</code>: The variable which must be defined by the script and set to the resource instance that will be used to render the page. </li>
+</ul>
+
+<p> A very simple Resource Script might look like: </p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyGreatResource</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;foo&lt;/html&gt;&quot;</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">MyGreatResource</span>()
+</pre>
+
+<p> A slightly more complicated resource script, which accesses some
+persistent data, might look like:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">SillyWeb</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Counter</span>
+
+<span class="py-src-variable">counter</span> = <span class="py-src-variable">registry</span>.<span class="py-src-variable">getComponent</span>(<span class="py-src-variable">Counter</span>)
+<span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">counter</span>:
+ <span class="py-src-variable">registry</span>.<span class="py-src-variable">setComponent</span>(<span class="py-src-variable">Counter</span>, <span class="py-src-variable">Counter</span>())
+<span class="py-src-variable">counter</span> = <span class="py-src-variable">registry</span>.<span class="py-src-variable">getComponent</span>(<span class="py-src-variable">Counter</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyResource</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">counter</span>.<span class="py-src-variable">increment</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;you are visitor %d&quot;</span> % <span class="py-src-variable">counter</span>.<span class="py-src-variable">getValue</span>()
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">MyResource</span>()
+</pre>
+
+<p> This is assuming you have the <code>SillyWeb.Counter</code> module,
+implemented something like the following:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>:
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">value</span> = <span class="py-src-number">0</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">increment</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">value</span> += <span class="py-src-number">1</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getValue</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">value</span>
+</pre>
+
+<h3>Web UIs<a name="auto17"/></h3>
+
+<p>
+The <a href="https://launchpad.net/nevow" shape="rect">Nevow</a> framework, available as
+part of the <a href="https://launchpad.net/quotient" shape="rect">Quotient</a> project,
+is an advanced system for giving Web UIs to your application. Nevow uses Twisted Web but is
+not itself part of Twisted.
+</p>
+
+<a name="SpreadableWebServers" shape="rect"/>
+<h3>Spreadable Web Servers<a name="auto18"/></h3>
+
+<p> One of the most interesting applications of Twisted Web is the distributed webserver; multiple servers can all answer requests on the same port, using the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.html" title="twisted.spread">twisted.spread</a></code> package for <q>spreadable</q> computing. In two different directories, run the commands: </p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web --user
+% twistd web --personal [other options, if you desire]
+</pre>
+
+<p> Once you're running both of these instances, go to <code>http://localhost:8080/your_username.twistd/</code> -- you will see the front page from the server you created with the <code>--personal</code> option. What's happening here is that the request you've sent is being relayed from the central (User) server to your own (Personal) server, over a PB connection. This technique can be highly useful for small <q>community</q> sites; using the code that makes this demo work, you can connect one HTTP port to multiple resources running with different permissions on the same machine, on different local machines, or even over the internet to a remote site. </p>
+
+<p>
+By default, a personal server listens on a UNIX socket in the owner's home
+directory. The <code class="shell">--port</code> option can be used to make
+it listen on a different address, such as a TCP or SSL server or on a UNIX
+server in a different location. If you use this option to make a personal
+server listen on a different address, the central (User) server won't be
+able to find it, but a custom server which uses the same APIs as the central
+server might. Another use of the <code class="shell">--port</code> option
+is to make the UNIX server robust against system crashes. If the server
+crashes and the UNIX socket is left on the filesystem, the personal server
+will not be able to restart until it is removed. However, if <code class="shell">--port unix:/home/username/.twistd-web-pb:wantPID=1</code> is
+supplied when creating the personal server, then a lockfile will be used to
+keep track of whether the server socket is in use and automatically delete
+it when it is not.
+</p>
+
+<h3>Serving PHP/Perl/CGI<a name="auto19"/></h3>
+
+<p>Everything related to CGI is located in
+the <code>twisted.web.twcgi</code>, and it's here you'll find the
+classes that you need to subclass in order to support the language of
+your (or somebody elses) taste. You'll also need to create your own
+kind of resource if you are using a non-unix operating system (such as
+Windows), or if the default resources has wrong pathnames to the
+parsers.</p>
+
+<p>The following snippet is a .rpy that serves perl-files. Look at <code>twisted.web.twcgi</code>
+for more examples regarding twisted.web and CGI.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">twcgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PerlScript</span>(<span class="py-src-parameter">twcgi</span>.<span class="py-src-parameter">FilteredScript</span>):
+ <span class="py-src-variable">filter</span> = <span class="py-src-string">'/usr/bin/perl'</span> <span class="py-src-comment"># Points to the perl parser</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/perlsite&quot;</span>) <span class="py-src-comment"># Points to the perl website</span>
+<span class="py-src-variable">resource</span>.<span class="py-src-variable">processors</span> = {<span class="py-src-string">&quot;.pl&quot;</span>: <span class="py-src-variable">PerlScript</span>} <span class="py-src-comment"># Files that end with .pl will be</span>
+ <span class="py-src-comment"># processed by PerlScript</span>
+<span class="py-src-variable">resource</span>.<span class="py-src-variable">indexNames</span> = [<span class="py-src-string">'index.pl'</span>]
+</pre>
+
+<h3>Serving WSGI Applications<a name="auto20"/></h3>
+
+<p><a href="http://wsgi.org/wsgi" shape="rect">WSGI</a> is the Web Server Gateway
+Interface. It is a specification for web servers and application servers to
+communicate with Python web applications. All modern Python web frameworks
+support the WSGI interface.</p>
+
+<p>The easiest way to get started with WSGI application is to use the twistd
+command:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd -n web --wsgi=helloworld.application
+</pre>
+
+<p>This assumes that you have a WSGI application called application in
+your helloworld module/package, which might look like this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">application</span>(<span class="py-src-parameter">environ</span>, <span class="py-src-parameter">start_response</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Basic WSGI Application&quot;&quot;&quot;</span>
+ <span class="py-src-variable">start_response</span>(<span class="py-src-string">'200 OK'</span>, [(<span class="py-src-string">'Content-type'</span>,<span class="py-src-string">'text/plain'</span>)])
+ <span class="py-src-keyword">return</span> [<span class="py-src-string">'Hello World!'</span>]
+</pre>
+
+<p>The above setup will be suitable for many applications where all that is
+needed is to server the WSGI application at the site's root. However, for
+greater control, Twisted provides support for using WSGI applications as
+resources <code class="api">twisted.web.wsgi.WSGIResource</code>.</p>
+
+<p>Here is an example of a WSGI application being served as the root resource
+for a site, in the following tac file:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">wsgi</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">WSGIResource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">threadpool</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ThreadPool</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">strports</span>
+
+<span class="py-src-comment"># Create and start a thread pool,</span>
+<span class="py-src-variable">wsgiThreadPool</span> = <span class="py-src-variable">ThreadPool</span>()
+<span class="py-src-variable">wsgiThreadPool</span>.<span class="py-src-variable">start</span>()
+
+<span class="py-src-comment"># ensuring that it will be stopped when the reactor shuts down</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">addSystemEventTrigger</span>(<span class="py-src-string">'after'</span>, <span class="py-src-string">'shutdown'</span>, <span class="py-src-variable">wsgiThreadPool</span>.<span class="py-src-variable">stop</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">application</span>(<span class="py-src-parameter">environ</span>, <span class="py-src-parameter">start_response</span>):
+ <span class="py-src-string">&quot;&quot;&quot;A basic WSGI application&quot;&quot;&quot;</span>
+ <span class="py-src-variable">start_response</span>(<span class="py-src-string">'200 OK'</span>, [(<span class="py-src-string">'Content-type'</span>,<span class="py-src-string">'text/plain'</span>)])
+ <span class="py-src-keyword">return</span> [<span class="py-src-string">'Hello World!'</span>]
+
+<span class="py-src-comment"># Create the WSGI resource</span>
+<span class="py-src-variable">wsgiAppAsResource</span> = <span class="py-src-variable">WSGIResource</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">wsgiThreadPool</span>, <span class="py-src-variable">application</span>)
+
+<span class="py-src-comment"># Hooks for twistd</span>
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'Twisted.web.wsgi Hello World Example'</span>)
+<span class="py-src-variable">server</span> = <span class="py-src-variable">strports</span>.<span class="py-src-variable">service</span>(<span class="py-src-string">'tcp:8080'</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">wsgiAppAsResource</span>))
+<span class="py-src-variable">server</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+</pre>
+
+<p>This can then be run like any other .tac file:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd -ny myapp.tac
+</pre>
+
+<p>Because of the synchronous nature of WSGI, each application call (for
+each request) is called within a thread, and the result is written back to the
+web server. For this, a <code class="api">twisted.python.threadpool.ThreadPool</code>
+instance is used.</p>
+
+<h3>Using VHostMonster<a name="auto21"/></h3>
+
+<p>It is common to use one server (for example, Apache) on a site with multiple
+names which then uses reverse proxy (in Apache, via <code>mod_proxy</code>) to different
+internal web servers, possibly on different machines. However, naive
+configuration causes miscommunication: the internal server firmly believes it
+is running on <q>internal-name:port</q>, and will generate URLs to that effect,
+which will be completely wrong when received by the client.</p>
+
+<p>While Apache has the ProxyPassReverse directive, it is really a hack
+and is nowhere near comprehensive enough. Instead, the recommended practice
+in case the internal web server is Twisted Web is to use VHostMonster.</p>
+
+<p>From the Twisted side, using VHostMonster is easy: just drop a file named
+(for example) <code>vhost.rpy</code> containing the following:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">vhost</span>
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">vhost</span>.<span class="py-src-variable">VHostMonsterResource</span>()
+</pre>
+
+<p>Make sure the web server is configured with the correct processors
+for the <code>rpy</code> extensions (the web server <code>twistd web
+--path</code> generates by default is so configured).</p>
+
+<p>From the Apache side, instead of using the following ProxyPass directive:</p>
+
+<pre xml:space="preserve">
+&lt;VirtualHost ip-addr&gt;
+ProxyPass / http://localhost:8538/
+ServerName example.com
+&lt;/VirtualHost&gt;
+</pre>
+
+<p>Use the following directive:</p>
+
+<pre xml:space="preserve">
+&lt;VirtualHost ip-addr&gt;
+ProxyPass / http://localhost:8538/vhost.rpy/http/example.com:80/
+ServerName example.com
+&lt;/VirtualHost&gt;
+</pre>
+
+<p>Here is an example for Twisted Web's reverse proxy:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">proxy</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">vhost</span>
+<span class="py-src-variable">vhostName</span> = <span class="py-src-string">'example.com'</span>
+<span class="py-src-variable">reverseProxy</span> = <span class="py-src-variable">proxy</span>.<span class="py-src-variable">ReverseProxyResource</span>(<span class="py-src-string">'internal'</span>, <span class="py-src-number">8538</span>,
+ <span class="py-src-string">'/vhost.rpy/http/'</span>+<span class="py-src-variable">vhostName</span>+<span class="py-src-string">'/'</span>)
+<span class="py-src-variable">root</span> = <span class="py-src-variable">vhost</span>.<span class="py-src-variable">NameVirtualHost</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">addHost</span>(<span class="py-src-variable">vhostName</span>, <span class="py-src-variable">reverseProxy</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web-proxy'</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<h2>Rewriting URLs<a name="auto22"/></h2>
+
+<p>Sometimes it is convenient to modify the content of
+the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">Request</a></code> object
+before passing it on. Because this is most often used to rewrite
+either the URL, the similarity to Apache's <code>mod_rewrite</code>
+has inspired the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.rewrite.html" title="twisted.web.rewrite">twisted.web.rewrite</a></code>
+module. Using this module is done via wrapping a resource with
+a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.rewrite.RewriterResource.html" title="twisted.web.rewrite.RewriterResource">twisted.web.rewrite.RewriterResource</a></code> which
+then has rewrite rules. Rewrite rules are functions which accept a
+request object, and possible modify it. After all rewrite rules run,
+the child resolution chain continues as if the wrapped resource,
+rather than the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.rewrite.RewriterResource.html" title="twisted.web.rewrite.RewriterResource">RewriterResource</a></code>, was the child.</p>
+
+<p>Here is an example, using the only rule currently supplied by Twisted
+itself:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">default_root</span> = <span class="py-src-variable">rewrite</span>.<span class="py-src-variable">RewriterResource</span>(<span class="py-src-variable">default</span>, <span class="py-src-variable">rewrite</span>.<span class="py-src-variable">tildeToUsers</span>)
+</pre>
+
+<p>This causes the URL <code>/~foo/bar.html</code> to be treated
+like <code>/users/foo/bar.html</code>. If done after setting
+default's <code>users</code> child to a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.distrib.UserDirectory.html" title="twisted.web.distrib.UserDirectory">distrib.UserDirectory</a></code>, it gives a
+configuration similar to the classical configuration of web server,
+common since the first NCSA servers.</p>
+
+<h2>Knowing When We're Not Wanted<a name="auto23"/></h2>
+
+<p>Sometimes it is useful to know when the other side has broken the connection.
+Here is an example which does that:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">util</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">println</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExampleResource</span>(<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;hello world&quot;</span>)
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">request</span>.<span class="py-src-variable">notifyFinish</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">println</span>(<span class="py-src-string">&quot;finished normally&quot;</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">println</span>, <span class="py-src-string">&quot;error&quot;</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">10</span>, <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">ExampleResource</span>()
+</pre>
+
+<p>This will allow us to run statistics on the log-file to see how many users
+are frustrated after merely 10 seconds.</p>
+
+<h2>As-Is Serving<a name="auto24"/></h2>
+
+<p>Sometimes, you want to be able to send headers and status
+directly. While you can do this with a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.script.ResourceScript.html" title="twisted.web.script.ResourceScript">ResourceScript</a></code>, an easier way is to
+use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.static.ASISProcessor.html" title="twisted.web.static.ASISProcessor">ASISProcessor</a></code>.
+Use it by, for example, adding it as a processor for
+the <code>.asis</code> extension. Here is a sample file:</p>
+
+<pre xml:space="preserve">
+HTTP/1.0 200 OK
+Content-Type: text/html
+
+Hello world
+</pre>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-development.html b/doc/web/howto/web-development.html
new file mode 100644
index 0000000..373d237
--- /dev/null
+++ b/doc/web/howto/web-development.html
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Web Application Development</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Web Application Development</h1>
+ <div class="toc"><ol><li><a href="#auto0">Code layout</a></li><li><a href="#auto1">Web application deployment</a></li><li><a href="#auto2">Understanding resource scripts (.rpy files)</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Code layout<a name="auto0"/></h2>
+
+<p>The development of a Twisted Web application should be orthogonal to its
+deployment. This means is that if you are developing a web application, it
+should be a resource with children, and internal links. Some of the children
+might use <a href="http://www.divmod.org/projects/nevow" shape="rect">Nevow</a>, some
+might be resources manually using <code>.write</code>, and so on. Regardless,
+the code should be in a Python module, or package, <em>outside</em> the web
+tree.</p>
+
+<p>You will probably want to test your application as you develop it. There are
+many ways to test, including dropping an <code>.rpy</code> which looks
+like:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">mypackage</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">toplevel</span>
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">toplevel</span>.<span class="py-src-variable">Resource</span>(<span class="py-src-variable">file</span>=<span class="py-src-string">&quot;foo/bar&quot;</span>, <span class="py-src-variable">color</span>=<span class="py-src-string">&quot;blue&quot;</span>)
+</pre>
+
+<p>into a directory, and then running:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web --path=/directory
+</pre>
+
+<p>You can also write a Python script like:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">mypackage</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">toplevel</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8080</span>,
+ <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">toplevel</span>.<span class="py-src-variable">Resource</span>(<span class="py-src-variable">file</span>=<span class="py-src-string">&quot;foo/bar&quot;</span>, <span class="py-src-variable">color</span>=<span class="py-src-string">&quot;blue&quot;</span>)))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h2>Web application deployment<a name="auto1"/></h2>
+
+<p>Which one of these development strategies you use is not terribly important,
+since (and this is the important part) deployment is <em>orthogonal</em>.
+Later, when you want users to actually <em>use</em> your code, you should worry
+about what to do -- or rather, don't. Users may have widely different needs.
+Some may want to run your code in a different process, so they'll use
+distributed web (<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.distrib.html" title="twisted.web.distrib">twisted.web.distrib</a></code>). Some may be
+using the <code>twisted-web</code> Debian package, and will drop in:</p>
+
+<pre class="shell" xml:space="preserve">
+% cat &gt; /etc/local.d/99addmypackage.py
+from mypackage import toplevel
+default.putChild(&quot;mypackage&quot;, toplevel.Resource(file=&quot;foo/bar&quot;, color=&quot;blue&quot;))
+^D
+</pre>
+
+<p>If you want to be friendly to your users, you can supply many examples in
+your package, like the above <code>.rpy</code> and the Debian-package drop-in.
+But the <em>ultimate</em> friendliness is to write a useful resource which does
+not have deployment assumptions built in.</p>
+
+<h2>Understanding resource scripts (<code>.rpy</code> files)<a name="auto2"/></h2>
+
+<p>Twisted Web is not PHP -- it has better tools for organizing code Python
+modules and packages, so use them. In PHP, the only tool for organizing code is
+a web page, which leads to silly things like PHP pages full of functions that
+other pages import, and so on. If you were to write your code this way with
+Twisted Web, you would do web development using many <code>.rpy</code> files,
+all importing some Python module. This is a <em>bad idea</em> -- it mashes
+deployment with development, and makes sure your users will be <em>tied</em> to
+the file-system.</p>
+
+<p>We have <code>.rpy</code>s because they are useful and necessary.
+But using them incorrectly leads to horribly unmaintainable
+applications. The best way to ensure you are using them correctly is
+to not use them at all, until you are on your <em>final</em>
+deployment stages. You should then find your <code>.rpy</code> files
+will be less than 10 lines, because you will not <em>have</em> more
+than 10 lines to write.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/asynchronous-deferred.html b/doc/web/howto/web-in-60/asynchronous-deferred.html
new file mode 100644
index 0000000..89c58b4
--- /dev/null
+++ b/doc/web/howto/web-in-60/asynchronous-deferred.html
@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Asynchronous Responses (via Deferred)</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Asynchronous Responses (via Deferred)</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The previous example had a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code> that generates its response
+asynchronously rather than immediately upon the call to its render
+method. Though it was a useful demonstration of the <code>NOT_DONE_YET</code>
+feature of Twisted Web, the example didn't reflect what a realistic application
+might want to do. This example introduces <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>, the Twisted class which is used
+to provide a uniform interface to many asynchronous events, and shows you an
+example of using a <code>Deferred</code>-returning API to generate an
+asynchronous response to a request in Twisted Web.</p>
+
+<p><code>Deferred</code> is the result of two consequences of the
+asynchronous programming approach. First, asynchronous code is
+frequently (if not always) concerned with some data (in Python, an
+object) which is not yet available but which probably will be
+soon. Asynchronous code needs a way to define what will be done to the
+object once it does exist. It also needs a way to define how to handle
+errors in the creation or acquisition of that object. These two needs
+are satisfied by the <i>callbacks</i> and <i>errbacks</i> of
+a <code>Deferred</code>. Callbacks are added to
+a <code>Deferred</code> with <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.addCallback.html" title="twisted.internet.defer.Deferred.addCallback">Deferred.addCallback</a></code>; errbacks
+are added with <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.addErrback.html" title="twisted.internet.defer.Deferred.addErrback">Deferred.addErrback</a></code>. When the
+object finally does exist, it is passed to <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.callback.html" title="twisted.internet.defer.Deferred.callback">Deferred.callback</a></code> which passes it
+on to the callback added with <code>addCallback</code>. Similarly, if
+an error occurs, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.errback.html" title="twisted.internet.defer.Deferred.errback">Deferred.errback</a></code> is called and
+the error is passed along to the errback added
+with <code>addErrback</code>. Second, the events that make
+asynchronous code actually work often take many different,
+incompatible forms. <code>Deferred</code> acts as the uniform
+interface which lets different parts of an asynchronous application
+interact and isolates them from implementation details they shouldn't
+be concerned with.</p>
+
+<p>That's almost all there is to <code>Deferred</code>. To solidify your new
+understanding, now consider this rewritten version
+of <code>DelayedResource</code> which uses a <code>Deferred</code>-based delay
+API. It does exactly the same thing as the <a href="asynchronous.html" shape="rect">previous
+example</a>. Only the implementation is different.</p>
+
+<p>First, the example must import that new API that was just mentioned, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.task.deferLater.html" title="twisted.internet.task.deferLater">deferLater</a></code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">task</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">deferLater</span>
+</pre>
+
+<p>Next, all the other imports (these are the same as last time):</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<p>With the imports done, here's the first part of
+the <code>DelayedResource</code> implementation. Again, this part of
+the code is identical to the previous version:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;Sorry to keep you waiting.&lt;/body&gt;&lt;/html&gt;&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+</pre>
+
+<p>Next we need to define the render method. Here's where things
+change a bit. Instead of using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTime.callLater.html" title="twisted.internet.interfaces.IReactorTime.callLater">callLater</a></code>,
+We're going to use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.task.deferLater.html" title="twisted.internet.task.deferLater">deferLater</a></code> this
+time. <code>deferLater</code> accepts a reactor, delay (in seconds, as
+with <code>callLater</code>), and a function to call after the delay
+to produce that elusive object discussed in the description
+of <code>Deferred</code>s. We're also going to
+use <code>_delayedRender</code> as the callback to add to
+the <code>Deferred</code> returned by <code>deferLater</code>. Since
+it expects the request object as an argument, we're going to set up
+the <code>deferLater</code> call to return a <code>Deferred</code>
+which has the request object as its result.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p>...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">deferLater</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">5</span>, <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">request</span>)
+</pre>
+
+<p>The <code>Deferred</code> referenced by <code>d</code> now needs to
+have the <code>_delayedRender</code> callback added to it. Once this
+is done, <code>_delayedRender</code> will be called with the result
+of <code>d</code> (which will be <code>request</code>, of course — the
+result of <code>(lambda: request)()</code>).</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p>...
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>)
+</pre>
+
+<p>Finally, the render method still needs to return <code>NOT_DONE_YET</code>,
+for exactly the same reasons as it did in the previous version of the
+example.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p>...
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+</pre>
+
+<p>And with that, <code>DelayedResource</code> is now implemented
+based on a <code>Deferred</code>. The example still isn't very
+realistic, but remember that since <code>Deferred</code>s offer a
+uniform interface to many different asynchronous event sources, this
+code now resembles a real application even more closely; you could
+easily replace <code>deferLater</code> with
+another <code>Deferred</code>-returning API and suddenly you might
+have a resource that does something useful.</p>
+
+<p>Finally, here's the complete, uninterrupted example source, as an rpy script:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">task</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">deferLater</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;Sorry to keep you waiting.&lt;/body&gt;&lt;/html&gt;&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">deferLater</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">5</span>, <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">request</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">DelayedResource</span>()
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/asynchronous.html b/doc/web/howto/web-in-60/asynchronous.html
new file mode 100644
index 0000000..c49999d
--- /dev/null
+++ b/doc/web/howto/web-in-60/asynchronous.html
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Asynchronous Responses</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Asynchronous Responses</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>In all of the previous examples, the resource examples presented generated
+responses immediately. One of the features of prime interest of Twisted Web,
+though, is the ability to generate a response over a longer period of time while
+leaving the server free to respond to other requests. In other words,
+asynchronously. In this installment, we'll write a resource like this.</p>
+
+<p>A resource that generates a response asynchronously looks like one that
+generates a response synchronously in many ways. The same base
+class, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>, is used
+either way; the same render methods are used. There are three basic differences,
+though.</p>
+
+<p>First, instead of returning the string which will be used as the
+body of the response, the resource uses <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.http.Request.write.html" title="twisted.web.http.Request.write">Request.write</a></code>. This method can be
+called repeatedly. Each call appends another string to the response
+body. Second, when the entire response body has been passed
+to <code>Request.write</code>, the application must
+call <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.http.Request.finish.html" title="twisted.web.http.Request.finish">Request.finish</a></code>. As you might expect
+from the name, this ends the response. Finally, in order to make
+Twisted Web not end the response as soon as the render method returns,
+the render method must return <code>NOT_DONE_YET</code>. Consider this
+example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;Sorry to keep you waiting.&lt;/body&gt;&lt;/html&gt;&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>, <span class="py-src-variable">request</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+</pre>
+
+<p>If you're not familiar with the reactor <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IReactorTime.callLater.html" title="twisted.internet.interfaces.IReactorTime.callLater">callLater</a></code>
+method, all you really need to know about it to understand this
+example is that the above usage of it arranges to
+have <code>self._delayedRender(request)</code> run about 5 seconds
+after <code>callLater</code> is invoked from this render method and
+that it returns immediately.</p>
+
+<p>All three of the elements mentioned earlier can be seen in this
+example. The resource uses <code>Request.write</code> to set the
+response body. It uses <code>Request.finish</code> after the entire
+body has been specified (all with just one call to write in this
+case). Lastly, it returns <code>NOT_DONE_YET</code> from its render
+method. So there you have it, asynchronous rendering with Twisted
+Web.</p>
+
+<p>Here's a complete rpy script based on this resource class (see the <a href="rpy-scripts.html" shape="rect">previous example</a> if you need a reminder about rpy
+scripts):</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;Sorry to keep you waiting.&lt;/body&gt;&lt;/html&gt;&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>, <span class="py-src-variable">request</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">DelayedResource</span>()
+</pre>
+
+<p>Drop this source into a <code>.rpy</code> file and fire up a server
+using <code>twistd -n web --path /directory/containing/script/.</code>
+You'll see that loading the page takes 5 seconds. If you try to load a
+second before the first completes, it will also take 5 seconds from
+the time you request it (but it won't be delayed by any other
+outstanding requests).</p>
+
+<p>Something else to consider when generating responses asynchronously is that
+the client may not wait around to get the response to its
+request. A <a href="interrupted.html" shape="rect">subsequent example</a> demonstrates how
+to detect that the client has abandoned the request and that the server
+shouldn't bother to finish generating its response.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/custom-codes.html b/doc/web/howto/web-in-60/custom-codes.html
new file mode 100644
index 0000000..05053f1
--- /dev/null
+++ b/doc/web/howto/web-in-60/custom-codes.html
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Custom Response Codes</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Custom Response Codes</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The previous example introduced <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.error.NoResource.html" title="twisted.web.error.NoResource">NoResource</a></code>, a Twisted Web error resource which
+responds with a 404 (not found) code. This example will cover the APIs
+that <code>NoResource</code> uses to do this so that you can generate your own
+custom response codes as desired.</p>
+
+<p>First, the now-standard import preamble:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<p>Now we'll define a new resource class that always returns a 402 (payment
+required) response. This is really not very different from the resources that
+was defined in previous examples. The fact that it has a response code other
+than 200 doesn't change anything else about its role. This will require using
+the request object, though, which none of the previous examples have done.</p>
+
+<p>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">Request</a></code> object has
+shown up in a couple of places, but so far we've ignored it. It is a parameter
+to the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.getChild.html" title="twisted.web.resource.Resource.getChild">getChild</a></code>
+API as well as to render methods such as <code>render_GET</code>. As you might
+have suspected, it represents the request for which a response is to be
+generated. Additionally, it also represents the response being generated. In
+this example we're going to use its <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.http.Request.setResponseCode.html" title="twisted.web.http.Request.setResponseCode">setResponseCode</a></code> method to - you guessed
+it - set the response's status code.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">PaymentRequired</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">setResponseCode</span>(<span class="py-src-number">402</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;Please swipe your credit card.&lt;/body&gt;&lt;/html&gt;&quot;</span>
+</pre>
+
+<p>Just like the other resources I've demonstrated, this one returns a
+string from its <code>render_GET</code> method to define the body of
+the response. All that's different is the call
+to <code>setResponseCode</code> to override the default response code,
+200, with a different one.</p>
+
+<p>Finally, the code to set up the site and reactor. We'll put an instance of
+the above defined resource at <code>/buy</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;buy&quot;</span>, <span class="py-src-variable">PaymentRequired</span>())
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Here's the complete example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PaymentRequired</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">setResponseCode</span>(<span class="py-src-number">402</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;Please swipe your credit card.&lt;/body&gt;&lt;/html&gt;&quot;</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;buy&quot;</span>, <span class="py-src-variable">PaymentRequired</span>())
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Run the server and visit <code>http://localhost:8880/buy</code> in your
+browser. It'll look pretty boring, but if you use Firefox's View Page Info
+right-click menu item (or your browser's equivalent), you'll be able to see that
+the server indeed sent back a 402 response code.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/dynamic-content.html b/doc/web/howto/web-in-60/dynamic-content.html
new file mode 100644
index 0000000..c099039
--- /dev/null
+++ b/doc/web/howto/web-in-60/dynamic-content.html
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Generating a Page Dynamically</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Generating a Page Dynamically</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The goal of this example is to show you how to dynamically generate the
+contents of a page.</p>
+
+<p>Taking care of some of the necessary imports first, we'll import <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code> and the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+</pre>
+
+<p>The Site is a factory which associates a listening port with the HTTP
+protocol implementation. The reactor is the main loop that drives any Twisted
+application; we'll use it to actually create the listening port in a moment.</p>
+
+<p>Next, we'll import one more thing from Twisted
+Web: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>. An
+instance of <code>Resource</code> (or a subclass) represents a page
+(technically, the entity addressed by a URI).</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+</pre>
+
+<p>Since we're going to make the demo resource a clock, we'll also import the
+time module:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+</pre>
+
+<p>With imports taken care of, the next step is to define
+a <code>Resource</code> subclass which has the dynamic rendering
+behavior we want. Here's a resource which generates a page giving the
+time:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ClockPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">isLeaf</span> = <span class="py-src-variable">True</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;%s&lt;/body&gt;&lt;/html&gt;&quot;</span> % (<span class="py-src-variable">time</span>.<span class="py-src-variable">ctime</span>(),)
+</pre>
+
+<p>Setting <code>isLeaf</code> to <code>True</code> indicates
+that <code>ClockPage</code> resources will never have any
+children.</p>
+
+<p>The <code>render_GET</code> method here will be called whenever the URI we
+hook this resource up to is requested with the <code>GET</code> method. The byte
+string it returns is what will be sent to the browser.</p>
+
+<p>With the resource defined, we can create a <code>Site</code> from it:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">resource</span> = <span class="py-src-variable">ClockPage</span>()
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>)
+</pre>
+
+<p>Just as with the previous static content example, this
+configuration puts our resource at the very top of the URI hierarchy,
+ie at <code>/</code>. With that <code>Site</code> instance, we can
+tell the reactor to <a href="../../../core/howto/servers.html" shape="rect">create
+a TCP server</a> and start servicing requests:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Here's the code with no interruptions:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClockPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">isLeaf</span> = <span class="py-src-variable">True</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;%s&lt;/body&gt;&lt;/html&gt;&quot;</span> % (<span class="py-src-variable">time</span>.<span class="py-src-variable">ctime</span>(),)
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">ClockPage</span>()
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/dynamic-dispatch.html b/doc/web/howto/web-in-60/dynamic-dispatch.html
new file mode 100644
index 0000000..cabd2da
--- /dev/null
+++ b/doc/web/howto/web-in-60/dynamic-dispatch.html
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Dynamic URL Dispatch</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Dynamic URL Dispatch</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>In the <a href="static-dispatch.html" shape="rect">previous example</a> we covered how to
+statically configure Twisted Web to serve different content at different
+URLs. The goal of this example is to show you how to do this dynamically
+instead. Reading the previous installment if you haven't already is suggested in
+order to get an overview of how URLs are treated when using Twisted Web's <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.html" title="twisted.web.resource">resource</a></code> APIs.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code> (the object which
+associates a listening server port with the HTTP implementation), <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code> (a convenient base class
+to use when defining custom pages), and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code> (the object which implements the Twisted
+main loop) return once again:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<p>With that out of the way, here's the interesting part of this
+example. We're going to define a resource which renders a whole-year
+calendar. The year it will render the calendar for will be the year in
+the request URL. So, for example, <code>/2009</code> will render a
+calendar for 2009. First, here's a resource that renders a calendar
+for the year passed to its initializer:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calendar</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">calendar</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">YearPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">year</span>):
+ <span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">year</span> = <span class="py-src-variable">year</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;&lt;pre&gt;%s&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&quot;</span> % (<span class="py-src-variable">calendar</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">year</span>),)
+</pre>
+
+<p>Pretty simple - not all that different from the first dynamic resource
+demonstrated in <a href="dynamic-content.html" shape="rect">Generating a Page
+Dynamically</a>. Now here's the resource that handles URLs with a year in them
+by creating a suitable instance of this <code>YearPage</code> class:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Calendar</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">YearPage</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">name</span>))
+</pre>
+
+<p>By implementing <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.getChild.html" title="twisted.web.resource.Resource.getChild">getChild</a></code> here, we've just defined
+how Twisted Web should find children of <code>Calendar</code> instances when
+it's resolving an URL into a resource. This implementation defines all integers
+as the children of <code>Calendar</code> (and punts on error handling, more on
+that later).</p>
+
+<p>All that's left is to create a <code>Site</code> using this resource as its
+root and then start the reactor:</p>
+
+<pre xml:space="preserve">
+root = Calendar()
+factory = Site(root)
+reactor.listenTCP(8880, factory)
+reactor.run()
+</pre>
+
+<p>And that's all. Any resource-based dynamic URL handling is going to look
+basically like <code>Calendar.getPage</code>. Here's the full example code:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">calendar</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">calendar</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">YearPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">year</span>):
+ <span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">year</span> = <span class="py-src-variable">year</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;&lt;pre&gt;%s&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&quot;</span> % (<span class="py-src-variable">calendar</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">year</span>),)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Calendar</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">YearPage</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">name</span>))
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Calendar</span>()
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/error-handling.html b/doc/web/howto/web-in-60/error-handling.html
new file mode 100644
index 0000000..6d56d3b
--- /dev/null
+++ b/doc/web/howto/web-in-60/error-handling.html
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Error Handling</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Error Handling</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>In this example we'll extend dynamic dispatch to return a 404 (not found)
+response when a client requests a non-existent URL.</p>
+
+<p>As in the previous examples, we'll start with <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code> imports:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<p>Next, we'll add one more import. <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.error.NoResource.html" title="twisted.web.error.NoResource">NoResource</a></code> is one of the pre-defined error
+resources provided by Twisted Web. It generates the necessary 404 response code
+and renders a simple html page telling the client there is no such resource.</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">error</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NoResource</span>
+</pre>
+
+<p>Next, we'll define a custom resource which does some dynamic URL
+dispatch. This example is going to be just like
+the <a href="dynamic-dispatch.html" shape="rect">previous one</a>, where the path segment is
+interpreted as a year; the difference is that this time we'll handle requests
+which don't conform to that pattern by returning the not found response:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Calendar</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">year</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">name</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">ValueError</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NoResource</span>()
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">YearPage</span>(<span class="py-src-variable">year</span>)
+</pre>
+
+<p>Aside from including the definition of <code>YearPage</code> from
+the previous example, the only other thing left to do is the
+normal <code>Site</code> and <code>reactor</code> setup. Here's the
+complete code for this example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">error</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NoResource</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">calendar</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">calendar</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">YearPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">year</span>):
+ <span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">year</span> = <span class="py-src-variable">year</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;&lt;pre&gt;%s&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&quot;</span> % (<span class="py-src-variable">calendar</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">year</span>),)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Calendar</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">year</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">name</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">ValueError</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NoResource</span>()
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">YearPage</span>(<span class="py-src-variable">year</span>)
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Calendar</span>()
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>This server hands out the same calendar views as the one from the previous
+installment, but it will also hand out a nice error page with a 404 response
+when a request is made for a URL which cannot be interpreted as a year.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/handling-posts.html b/doc/web/howto/web-in-60/handling-posts.html
new file mode 100644
index 0000000..244f6b5
--- /dev/null
+++ b/doc/web/howto/web-in-60/handling-posts.html
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Handling POSTs</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Handling POSTs</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>All of the previous examples have focused on <code>GET</code>
+requests. Unlike <code>GET</code> requests, <code>POST</code> requests can have
+a request body - extra data after the request headers; for example, data
+representing the contents of an HTML form. Twisted Web makes this data available
+to applications via the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">Request</a></code> object.</p>
+
+<p>Here's an example web server which renders a static HTML form and then
+generates a dynamic page when that form is posted back to it. Disclaimer: While
+it's convenient for this example, it's often not a good idea to make a resource
+that <code>POST</code>s to itself; this isn't about Twisted Web, but the nature
+of HTTP in general; if you do this in a real application, make sure you
+understand the possible negative consequences.</p>
+
+<p>As usual, we start with some imports. In addition to the Twisted imports,
+this example uses the <code>cgi</code> module to <a href="http://en.wikipedia.org/wiki/Cross-site_scripting" shape="rect">escape user-entered
+content</a> for inclusion in the output.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+</pre>
+
+<p>Next, we'll define a resource which is going to do two things. First, it will
+respond to <code>GET</code> requests with a static HTML form:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">FormPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;html&gt;&lt;body&gt;&lt;form method=&quot;POST&quot;&gt;&lt;input name=&quot;the-field&quot; type=&quot;text&quot; /&gt;&lt;/form&gt;&lt;/body&gt;&lt;/html&gt;'</span>
+</pre>
+
+<p>This is similar to the resource used in a <a href="dynamic-content.html" shape="rect">previous installment</a>. However, we'll now add
+one more method to give it a second behavior; this <code>render_POST</code>
+method will allow it to accept <code>POST</code> requests:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p>...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_POST</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;html&gt;&lt;body&gt;You submitted: %s&lt;/body&gt;&lt;/html&gt;'</span> % (<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">args</span>[<span class="py-src-string">&quot;the-field&quot;</span>][<span class="py-src-number">0</span>]),)
+</pre>
+
+<p>The main thing to note here is the use
+of <code>request.args</code>. This is a dictionary-like object that
+provides access to the contents of the form. The keys in this
+dictionary are the names of inputs in the form. Each value is a list
+containing strings (since there can be multiple inputs with the same
+name), which is why we had to extract the first element to pass
+to <code>cgi.escape</code>. <code>request.args</code> will be
+populated from form contents whenever a <code>POST</code> request is
+made with a content type
+of <code>application/x-www-form-urlencoded</code>
+or <code>multipart/form-data</code> (it's also populated by query
+arguments for any type of request).</p>
+
+<p>Finally, the example just needs the usual site creation and port setup:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;form&quot;</span>, <span class="py-src-variable">FormPage</span>())
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Run the server and
+visit <a href="http://localhost:8880/form" shape="rect">http://localhost:8880/form</a>,
+submit the form, and watch it generate a page including the value you entered
+into the single field.</p>
+
+<p>Here's the complete source for the example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FormPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;html&gt;&lt;body&gt;&lt;form method=&quot;POST&quot;&gt;&lt;input name=&quot;the-field&quot; type=&quot;text&quot; /&gt;&lt;/form&gt;&lt;/body&gt;&lt;/html&gt;'</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_POST</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;html&gt;&lt;body&gt;You submitted: %s&lt;/body&gt;&lt;/html&gt;'</span> % (<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">args</span>[<span class="py-src-string">&quot;the-field&quot;</span>][<span class="py-src-number">0</span>]),)
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;form&quot;</span>, <span class="py-src-variable">FormPage</span>())
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/http-auth.html b/doc/web/howto/web-in-60/http-auth.html
new file mode 100644
index 0000000..97d4581
--- /dev/null
+++ b/doc/web/howto/web-in-60/http-auth.html
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: HTTP Authentication</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">HTTP Authentication</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>Many of the previous examples have looked at how to serve content by using
+existing resource classes or implementing new ones. In this example we'll use
+Twisted Web's basic or digest HTTP authentication to control access to these
+resources.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.guard.html" title="twisted.web.guard">guard</a></code>, the Twisted Web
+module which provides most of the APIs that will be used in this
+example, helps you to
+add <a href="http://en.wikipedia.org/wiki/Authentication" shape="rect">authentication</a>
+and <a href="http://en.wikipedia.org/wiki/Authorization" shape="rect">authorization</a>
+to a resource hierarchy. It does this by providing a resource which
+implements <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.getChild.html" title="twisted.web.resource.Resource.getChild">getChild</a></code> to return
+a <a href="dynamic-dispatch.html" shape="rect">dynamically selected
+resource</a>. The selection is based on the authentication headers in
+the request. If those headers indicate that the request is made on
+behalf of Alice, then Alice's resource will be returned. If they
+indicate that it was made on behalf of Bob, his will be returned. If
+the headers contain invalid credentials, an error resource is
+returned. Whatever happens, once this resource is returned, URL
+traversal continues as normal from that resource.</p>
+
+<p>The resource that implements this is <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.guard.HTTPAuthSessionWrapper.html" title="twisted.web.guard.HTTPAuthSessionWrapper">HTTPAuthSessionWrapper</a></code>, though it is directly
+responsible for very little of the process. It will extract headers from the
+request and hand them off to a credentials factory to parse them according to
+the appropriate standards (eg <a href="http://tools.ietf.org/html/rfc2617" shape="rect">HTTP
+Authentication: Basic and Digest Access Authentication</a>) and then hand the
+resulting credentials object off to a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.Portal.html" title="twisted.cred.portal.Portal">Portal</a></code>, the core
+of <a href="../../../core/howto/cred.html" shape="rect">Twisted Cred</a>, a system for
+uniform handling of authentication and authorization. We won't discuss Twisted
+Cred in much depth here. To make use of it with Twisted Web, the only thing you
+really need to know is how to implement an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.IRealm.html" title="twisted.cred.portal.IRealm">IRealm</a></code>.</p>
+
+<p>You need to implement a realm because the realm is the object that
+actually decides which resources are used for which users. This can be
+as complex or as simple as it suitable for your application. For this
+example we'll keep it very simple: each user will have a resource
+which is a static file listing of the <code>public_html</code>
+directory in their UNIX home directory. First, we need to
+import <code>implements</code> from <code>zope.interface</code>
+and <code>IRealm</code>
+from <code>twisted.cred.portal</code>. Together these will let me mark
+this class as a realm (this is mostly - but not entirely - a
+documentation thing). We'll also need <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.static.File.html" title="twisted.web.static.File">File</a></code> for the actual implementation
+later.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PublicHTMLRealm</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
+</pre>
+
+<p>A realm only needs to implement one method: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.IRealm.requestAvatar.html" title="twisted.cred.portal.IRealm.requestAvatar">requestAvatar</a></code>. This method is called
+after any successful authentication attempt (ie, Alice supplied the right
+password). Its job is to return the <i>avatar</i> for the user who succeeded in
+authenticating. An <i>avatar</i> is just an object that represents a user. In
+this case, it will be a <code>File</code>. In general, with <code>Guard</code>,
+the avatar must be a resource of some sort.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p>...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">IResource</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-keyword">return</span> (<span class="py-src-variable">IResource</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/home/%s/public_html&quot;</span> % (<span class="py-src-variable">avatarId</span>,)), <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">None</span>)
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>()
+</pre>
+
+<p>A few notes on this method:</p>
+<ul>
+ <li>The <code>avatarId</code> parameter is essentially the username. It's the
+ job of some other code to extract the username from the request headers and
+ make sure it gets passed here.</li>
+ <li>The <code>mind</code> is always <code>None</code> when writing a realm to
+ be used with <code>Guard</code>. You can ignore it until you want to write a
+ realm for something else.</li>
+ <li><code>Guard</code> is always
+ passed <code class="twisted.web.resource">IResource</code> as
+ the <code>interfaces</code> parameter. If <code>interfaces</code> only
+ contains interfaces your code doesn't understand,
+ raising <code>NotImplementedError</code> is the thing to do, as
+ above. You'll only need to worry about getting a different interface when
+ you write a realm for something other than <code>Guard</code>.</li>
+ <li>If you want to track when a user logs out, that's what the last element of
+ the returned tuple is for. It will be called when this avatar logs
+ out. <code>lambda: None</code> is the idiomatic no-op logout function.</li>
+ <li>Notice that the path handling code in this example is written very
+ poorly. This example may be vulnerable to certain unintentional information
+ disclosure attacks. This sort of problem is exactly the
+ reason <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.filepath.FilePath.html" title="twisted.python.filepath.FilePath">FilePath</a></code>
+ exists. However, that's an example for another day...</li>
+</ul>
+
+<p>We're almost ready to set up the resource for this example. To
+create an <code>HTTPAuthSessionWrapper</code>, though, we need two
+things. First, a portal, which requires the realm above, plus at least
+one credentials checker:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePasswordDB</span>
+
+<span class="py-src-variable">portal</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">PublicHTMLRealm</span>(), [<span class="py-src-variable">FilePasswordDB</span>(<span class="py-src-string">'httpd.password'</span>)])
+</pre>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.checkers.FilePasswordDB.html" title="twisted.cred.checkers.FilePasswordDB">FilePasswordDB</a></code> is the
+credentials checker. It knows how to read <code>passwd(5)</code>-style (loosely)
+files to check credentials against. It is responsible for the authentication
+work after <code>HTTPAuthSessionWrapper</code> extracts the credentials from the
+request.</p>
+
+<p>Next we need either <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.guard.BasicCredentialFactory.html" title="twisted.web.guard.BasicCredentialFactory">BasicCredentialFactory</a></code>
+or <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.guard.DigestCredentialFactory.html" title="twisted.web.guard.DigestCredentialFactory">DigestCredentialFactory</a></code>. The former
+knows how to challenge HTTP clients to do basic authentication; the
+latter, digest authentication. We'll use digest here:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DigestCredentialFactory</span>
+
+<span class="py-src-variable">credentialFactory</span> = <span class="py-src-variable">DigestCredentialFactory</span>(<span class="py-src-string">&quot;md5&quot;</span>, <span class="py-src-string">&quot;example.org&quot;</span>)
+</pre>
+
+<p>The two parameters to this constructor are the hash algorithm and
+the HTTP authentication realm which will be used. The only other valid
+hash algorithm is &quot;sha&quot; (but be careful, MD5 is more widely supported
+than SHA). The HTTP authentication realm is mostly just a string that
+is presented to the user to let them know why they're authenticating
+(you can read more about this in
+the <a href="http://tools.ietf.org/html/rfc2617" shape="rect">RFC</a>).</p>
+
+<p>With those things created, we can finally
+instantiate <code>HTTPAuthSessionWrapper</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">HTTPAuthSessionWrapper</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">HTTPAuthSessionWrapper</span>(<span class="py-src-variable">portal</span>, [<span class="py-src-variable">credentialFactory</span>])
+</pre>
+
+<p>There's just one last thing that needs to be done
+here. When <a href="rpy-scripts.html" shape="rect">rpy scripts</a> were
+introduced, it was mentioned that they are evaluated in an unusual
+context. This is the first example that actually needs to take this
+into account. It so happens that <code>DigestCredentialFactory</code>
+instances are stateful. Authentication will only succeed if the same
+instance is used to both generate challenges and examine the responses
+to those challenges. However, the normal mode of operation for an rpy
+script is for it to be re-executed for every request. This leads to a
+new <code>DigestCredentialFactory</code> being created for every request, preventing
+any authentication attempt from ever succeeding.</p>
+
+<p>There are two ways to deal with this. First, and the better of the two ways,
+we could move almost all of the code into a real Python module, including the
+code that instantiates the <code>DigestCredentialFactory</code>. This would
+ensure that the same instance was used for every request. Second, and the easier
+of the two ways, we could add a call to <code>cache()</code> to the beginning of
+the rpy script:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">cache</span>()
+</pre>
+
+<p><code>cache</code> is part of the globals of any rpy script, so you don't
+need to import it (it's okay to be cringing at this
+point). Calling <code>cache</code> makes Twisted re-use the result of the first
+evaluation of the rpy script for subsequent requests too - just what we want in
+this case.</p>
+
+<p>Here's the complete example (with imports re-arranged to the more
+conventional style):</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-variable">cache</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>, <span class="py-src-variable">Portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePasswordDB</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IResource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">HTTPAuthSessionWrapper</span>, <span class="py-src-variable">DigestCredentialFactory</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PublicHTMLRealm</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">IResource</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-keyword">return</span> (<span class="py-src-variable">IResource</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/home/%s/public_html&quot;</span> % (<span class="py-src-variable">avatarId</span>,)), <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">None</span>)
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>()
+
+<span class="py-src-variable">portal</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">PublicHTMLRealm</span>(), [<span class="py-src-variable">FilePasswordDB</span>(<span class="py-src-string">'httpd.password'</span>)])
+
+<span class="py-src-variable">credentialFactory</span> = <span class="py-src-variable">DigestCredentialFactory</span>(<span class="py-src-string">&quot;md5&quot;</span>, <span class="py-src-string">&quot;localhost:8080&quot;</span>)
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">HTTPAuthSessionWrapper</span>(<span class="py-src-variable">portal</span>, [<span class="py-src-variable">credentialFactory</span>])
+</pre>
+
+<p>And voila, a password-protected per-user Twisted Web server.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/index.html b/doc/web/howto/web-in-60/index.html
new file mode 100644
index 0000000..e091b18
--- /dev/null
+++ b/doc/web/howto/web-in-60/index.html
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Web In 60 Seconds</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Web In 60 Seconds</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<p>This set of examples contains short, complete applications
+of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.html" title="twisted.web">twisted.web</a></code>. For subjects not covered
+here, see the <a href="../using-twistedweb.html" shape="rect">Twisted Web
+tutorial</a> and the API documentation.</p>
+
+<ol>
+<li><a href="static-content.html" shape="rect">Serving static content from a directory</a></li>
+<li><a href="dynamic-content.html" shape="rect">Generating a page dynamically</a></li>
+<li><a href="static-dispatch.html" shape="rect">Static URL dispatch</a></li>
+<li><a href="dynamic-dispatch.html" shape="rect">Dynamic URL dispatch</a></li>
+<li><a href="error-handling.html" shape="rect">Error handling</a></li>
+<li><a href="custom-codes.html" shape="rect">Custom response codes</a></li>
+<li><a href="handling-posts.html" shape="rect">Handling POSTs</a></li>
+<li><a href="rpy-scripts.html" shape="rect">rpy scripts (or, how to save yourself some typing)</a></li>
+<li><a href="asynchronous.html" shape="rect">Asynchronous responses</a></li>
+<li><a href="asynchronous-deferred.html" shape="rect">Asynchronous responses (via Deferred)</a></li>
+<li><a href="interrupted.html" shape="rect">Interrupted responses</a></li>
+<li><a href="logging-errors.html" shape="rect">Logging errors</a></li>
+<li><a href="wsgi.html" shape="rect">WSGIs</a></li>
+<li><a href="http-auth.html" shape="rect">HTTP authentication</a></li>
+<li><a href="session-basics.html" shape="rect">Session basics</a></li>
+<li><a href="session-store.html" shape="rect">Storing objects in the session</a></li>
+<li><a href="session-endings.html" shape="rect">Session endings</a></li>
+</ol>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/interrupted.html b/doc/web/howto/web-in-60/interrupted.html
new file mode 100644
index 0000000..f4701a3
--- /dev/null
+++ b/doc/web/howto/web-in-60/interrupted.html
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Interrupted Responses</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Interrupted Responses</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The previous example had a Resource that generates its response
+asynchronously rather than immediately upon the call to its render method. When
+generating responses asynchronously, the possibility is introduced that the
+connection to the client may be lost before the response is generated. In such a
+case, it is often desirable to abandon the response generation entirely, since
+there is nothing to do with the data once it is produced. This example shows how
+to be notified that the connection has been lost.</p>
+
+<p>This example will build upon the <a href="asynchronous.html" shape="rect">asynchronous
+responses example</a> which simply (if not very realistically) generated its
+response after a fixed delay. We will expand that resource so that as soon as
+the client connection is lost, the delayed event is cancelled and the response
+is never generated.</p>
+
+<p>The feature this example relies on is provided by another <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">Request</a></code> method: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.http.Request.notifyFinish.html" title="twisted.web.http.Request.notifyFinish">notifyFinish</a></code>. This method returns a new
+Deferred which will fire with <code>None</code> if the request is successfully
+responded to or with an error otherwise - for example if the connection is lost
+before the response is sent.</p>
+
+<p>The example starts in a familiar way, with the requisite Twisted imports and
+a resource class with the same <code>_delayedRender</code> used previously:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;Sorry to keep you waiting.&lt;/body&gt;&lt;/html&gt;&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+</pre>
+
+<p>Before defining the render method, we're going to define an errback
+(an errback being a callback that gets called when there's an error),
+though. This will be the errback attached to the <code>Deferred</code>
+returned by <code>Request.notifyFinish</code>. It will cancel the
+delayed call to <code>_delayedRender</code>.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p>...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_responseFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">err</span>, <span class="py-src-parameter">call</span>):
+ <span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+</pre>
+
+<p>Finally, the render method will set up the delayed call just as it
+did before, and return <code>NOT_DONE_YET</code> likewise. However, it
+will also use <code>Request.notifyFinish</code> to make
+sure <code>_responseFailed</code> is called if appropriate.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p>...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>, <span class="py-src-variable">request</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">notifyFinish</span>().<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_responseFailed</span>, <span class="py-src-variable">call</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+</pre>
+
+<p>Notice that since <code>_responseFailed</code> needs a reference to
+the delayed call object in order to cancel it, we passed that object
+to <code>addErrback</code>. Any additional arguments passed
+to <code>addErrback</code> (or <code>addCallback</code>) will be
+passed along to the errback after the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code> instance which is always
+passed as the first argument. Passing <code>call</code> here means it
+will be passed to <code>_responseFailed</code>, where it is expected
+and required.</p>
+
+<p>That covers almost all the code for this example. Here's the entire example
+without interruptions, as an <a href="rpy-scripts.html" shape="rect">rpy script</a>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;Sorry to keep you waiting.&lt;/body&gt;&lt;/html&gt;&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_responseFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">err</span>, <span class="py-src-parameter">call</span>):
+ <span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>, <span class="py-src-variable">request</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">notifyFinish</span>().<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_responseFailed</span>, <span class="py-src-variable">call</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">DelayedResource</span>()
+</pre>
+
+<p>Toss this into <code>example.rpy</code>, fire it up with <code>twistd -n
+web --path .</code>, and
+hit <a href="http://localhost:8080/example.rpy" shape="rect">http://localhost:8080/example.rpy</a>. If
+you wait five seconds, you'll get the page content. If you interrupt the request
+before then, say by hitting escape (in Firefox, at least), then you'll see
+perhaps the most boring demonstration ever - no page content, and nothing in the
+server logs. Success!</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/logging-errors.html b/doc/web/howto/web-in-60/logging-errors.html
new file mode 100644
index 0000000..2440a04
--- /dev/null
+++ b/doc/web/howto/web-in-60/logging-errors.html
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Logging Errors</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Logging Errors</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The <a href="interrupted.html" shape="rect">previous example</a> created a server that
+dealt with response errors by aborting response generation, potentially avoiding
+pointless work. However, it did this silently for any error. In this example,
+we'll modify the previous example so that it logs each failed response.</p>
+
+<p>This example will use the Twisted API for logging errors. As was
+mentioned in the <a href="asynchronous-deferred.html" shape="rect">first example
+covering Deferreds</a>, errbacks are passed an error. In the previous
+example, the <code>_responseFailed</code> errback accepted this error
+as a parameter but ignored it. The only way this example will differ
+is that this <code>_responseFailed</code> will use that error
+parameter to log a message.</p>
+
+<p>This example will require all of the imports required by the previous example
+plus one new import:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">err</span>
+</pre>
+
+<p>The only other part of the previous example which changes is
+the <code>_responseFailed</code> callback, which will now log the
+error passed to it:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p>...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_responseFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>, <span class="py-src-parameter">call</span>):
+ <span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+ <span class="py-src-variable">err</span>(<span class="py-src-variable">failure</span>, <span class="py-src-string">&quot;Async response demo interrupted response&quot;</span>)
+</pre>
+
+<p>We're passing two arguments to <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.log.err.html" title="twisted.python.log.err">err</a></code> here. The first is the error which is being
+passed in to the callback. This is always an object of type <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code>, a class which represents an
+exception and (sometimes, but not always) a traceback. <code>err</code> will
+format this nicely for the log. The second argument is a descriptive string that
+tells someone reading the log what the source of the error was.</p>
+
+<p>Here's the full example with the two above modifications:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">err</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;Sorry to keep you waiting.&lt;/body&gt;&lt;/html&gt;&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_responseFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>, <span class="py-src-parameter">call</span>):
+ <span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+ <span class="py-src-variable">err</span>(<span class="py-src-variable">failure</span>, <span class="py-src-string">&quot;Async response demo interrupted response&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>, <span class="py-src-variable">request</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">notifyFinish</span>().<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_responseFailed</span>, <span class="py-src-variable">call</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">DelayedResource</span>()
+</pre>
+
+<p>Run this server as in the <a href="interrupted.html" shape="rect">previous example</a>
+and interrupt a request. Unlike the previous example, where the server gave no
+indication that this had happened, you'll see a message in the log output with
+this version.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/rpy-scripts.html b/doc/web/howto/web-in-60/rpy-scripts.html
new file mode 100644
index 0000000..7649771
--- /dev/null
+++ b/doc/web/howto/web-in-60/rpy-scripts.html
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: rpy scripts (or, how to save yourself some typing)</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">rpy scripts (or, how to save yourself some typing)</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The goal of this installment is to show you another way to run a Twisted Web
+server with a custom resource which doesn't require as much code as the previous
+examples.</p>
+
+<p>The feature in question is called an <code>rpy script</code>. An rpy script
+is a Python source file which defines a resource and can be loaded into a
+Twisted Web server. The advantages of this approach are that you don't have to
+write code to create the site or set up a listening port with the reactor. That
+means fewer lines of code that aren't dedicated to the task you're trying to
+accomplish.</p>
+
+<p>There are some disadvantages, though. An rpy script must have the
+extension <code>.rpy</code>. This means you can't import it using the
+usual Python import statement. This means it's hard to re-use code in
+an rpy script. This also means you can't easily unit test it. The code
+in an rpy script is evaluated in an unusual context. So, while rpy
+scripts may be useful for testing out ideas, they're not recommend for
+much more than that.</p>
+
+<p>Okay, with that warning out of the way, let's dive in. First, as mentioned,
+rpy scripts are Python source files with the <code>.rpy</code> extension. So,
+open up an appropriately named file (for example, <code>example.rpy</code>) and
+put this code in it:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClockPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">isLeaf</span> = <span class="py-src-variable">True</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;&lt;body&gt;%s&lt;/body&gt;&lt;/html&gt;&quot;</span> % (<span class="py-src-variable">time</span>.<span class="py-src-variable">ctime</span>(),)
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">ClockPage</span>()
+</pre>
+
+<p>You may recognize this as the resource from
+the <a href="dynamic-content.html" shape="rect">first dynamic rendering
+example</a>. What's different is what you don't see: we didn't
+import <code>reactor</code> or <code>Site</code>. There are no calls
+to <code>listenTCP</code> or <code>run</code>. Instead, and this is
+the core idea for rpy scripts, we just bound the
+name <code>resource</code> to the resource we want the script to
+serve. Every rpy script must bind this name, and this name is the only
+thing Twisted Web will pay attention to in an rpy script.</p>
+
+<p>All that's left is to drop this rpy script into a Twisted Web server. There
+are a few ways to do this. The simplest way is with <code>twistd</code>:</p>
+
+<pre class="shell" xml:space="preserve">
+$ twistd -n web --path .
+</pre>
+
+<p>Hit
+<a href="http://localhost:8080/example.rpy" shape="rect">http://localhost:8080/example.rpy</a>
+to see it run. You can pass other arguments here too. <code>twistd web</code>
+has options for specifying which port number to bind, whether to set up an HTTPS
+server, and plenty more. Other options you can pass to <code>twistd</code> allow
+you to configure logging to work differently, to select a different reactor,
+etc. For a full list of options, see <code>twistd --help</code> and <code>twistd
+web --help</code>.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/session-basics.html b/doc/web/howto/web-in-60/session-basics.html
new file mode 100644
index 0000000..d468a0e
--- /dev/null
+++ b/doc/web/howto/web-in-60/session-basics.html
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Session Basics</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Session Basics</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>Sessions are the most complicated topic covered in this series of examples,
+and because of that it is going to take a few examples to cover all of the
+different aspects. This first example demonstrates the very basics of the
+Twisted Web session API: how to get the session object for the current request
+and how to prematurely expire a session.</p>
+
+<p>Before diving into the APIs, let's look at the big picture of
+sessions in Twisted Web. Sessions are represented by instances
+of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Session.html" title="twisted.web.server.Session">Session</a></code>. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code> creates a new instance
+of <code>Session</code> the first time an application asks for it for
+a particular session. <code>Session</code> instances are kept on
+the <code>Site</code> instance until they expire (due to inactivity or
+because they are explicitly expired). Each time after the first that a
+particular session's <code>Session</code> object is requested, it is
+retrieved from the <code>Site</code>.</p>
+
+<p>With the conceptual underpinnings of the upcoming API in place, here comes
+the example. This will be a very simple <a href="rpy-scripts.html" shape="rect">rpy
+script</a> which tells a user what its unique session identifier is and lets it
+prematurely expire the session.</p>
+
+<p>First, we'll import <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code> so we can define a couple of
+subclasses of it:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+</pre>
+
+<p>Next we'll define the resource which tells the client what its session
+identifier is. This is done easily by first getting the session object
+using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Request.getSession.html" title="twisted.web.server.Request.getSession">Request.getSession</a></code> and
+then getting the session object's uid attribute:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ShowSession</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Your session id is: '</span> + <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>().<span class="py-src-variable">uid</span>
+</pre>
+
+<p>To let the client expire its own session before it times out, we'll define
+another resource which expires whatever session it is requested with. This is
+done using the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Session.expire.html" title="twisted.web.server.Session.expire">Session.expire</a></code>
+method:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ExpireSession</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>().<span class="py-src-variable">expire</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Your session has been expired.'</span>
+</pre>
+
+<p>Finally, to make the example an rpy script, we'll make an instance
+of <code>ShowSession</code> and give it an instance
+of <code>ExpireSession</code> as a child using <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.putChild.html" title="twisted.web.resource.Resource.putChild">Resource.putChild</a></code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">resource</span> = <span class="py-src-variable">ShowSession</span>()
+<span class="py-src-variable">resource</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;expire&quot;</span>, <span class="py-src-variable">ExpireSession</span>())
+</pre>
+
+<p>And that is the complete example. You can fire this up and load the top
+page. You'll see a (rather opaque) session identifier that remains the same
+across reloads (at least until you flush the <code>TWISTED_SESSION</code> cookie
+from your browser or enough time passes). You can then visit
+the <code>expire</code> child and go back to the top page and see that you have
+a new session.</p>
+
+<p>Here's the complete source for the example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ShowSession</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Your session id is: '</span> + <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>().<span class="py-src-variable">uid</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExpireSession</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>().<span class="py-src-variable">expire</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Your session has been expired.'</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">ShowSession</span>()
+<span class="py-src-variable">resource</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;expire&quot;</span>, <span class="py-src-variable">ExpireSession</span>())
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/session-endings.html b/doc/web/howto/web-in-60/session-endings.html
new file mode 100644
index 0000000..082acd4
--- /dev/null
+++ b/doc/web/howto/web-in-60/session-endings.html
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Session Endings</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Session Endings</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The previous two examples introduced Twisted Web's session APIs. This
+included accessing the session object, storing state on it, and retrieving it
+later, as well as the idea that the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Session.html" title="twisted.web.server.Session">Session</a></code> object has a lifetime which is tied to
+the notional session it represents. This example demonstrates how to exert some
+control over that lifetime and react when it expires.</p>
+
+<p>The lifetime of a session is controlled by the <code>sessionTimeout</code>
+attribute of the <code>Session</code> class. This attribute gives the number of
+seconds a session may go without being accessed before it expires. The default
+is 15 minutes. In this example we'll change that to a different value.</p>
+
+<p>One way to override the value is with a subclass:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Session</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ShortSession</span>(<span class="py-src-parameter">Session</span>):
+ <span class="py-src-variable">sessionTimeout</span> = <span class="py-src-number">60</span>
+</pre>
+
+<p>To have Twisted Web actually make use of this session class, rather
+than the default, it is also necessary to override
+the <code>sessionFactory</code> attribute of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code>. We could do this with another
+subclass, but we could also do it to just one instance
+of <code>Site</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">rootResource</span>)
+<span class="py-src-variable">factory</span>.<span class="py-src-variable">sessionFactory</span> = <span class="py-src-variable">ShortSession</span>
+</pre>
+
+<p>Sessions given out for requests served by this <code>Site</code> will
+use <code>ShortSession</code> and only last one minute without activity.</p>
+
+<p>You can have arbitrary functions run when sessions expire,
+too. This can be useful for cleaning up external resources associated
+with the session, tracking usage statistics, and more. This
+functionality is provided via <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Session.notifyOnExpire.html" title="twisted.web.server.Session.notifyOnExpire">Session.notifyOnExpire</a></code>. It accepts a
+single argument: a function to call when the session expires. Here's a
+trivial example which prints a message whenever a session expires:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExpirationLogger</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">sessions</span> = <span class="py-src-variable">set</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">session</span> = <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>()
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>.<span class="py-src-variable">add</span>(<span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span>)
+ <span class="py-src-variable">session</span>.<span class="py-src-variable">notifyOnExpire</span>(<span class="py-src-keyword">lambda</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">_expired</span>(<span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_expired</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">uid</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Session&quot;</span>, <span class="py-src-variable">uid</span>, <span class="py-src-string">&quot;has expired.&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">uid</span>)
+</pre>
+
+<p>Keep in mind that using a method as the callback will keep the instance (in
+this case, the <code>ExpirationLogger</code> resource) in memory until the
+session expires.</p>
+
+<p>With those pieces in hand, here's an example that prints a message whenever a
+session expires, and uses sessions which last for 5 seconds:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>, <span class="py-src-variable">Session</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ShortSession</span>(<span class="py-src-parameter">Session</span>):
+ <span class="py-src-variable">sessionTimeout</span> = <span class="py-src-number">5</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExpirationLogger</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">sessions</span> = <span class="py-src-variable">set</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">session</span> = <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>()
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>.<span class="py-src-variable">add</span>(<span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span>)
+ <span class="py-src-variable">session</span>.<span class="py-src-variable">notifyOnExpire</span>(<span class="py-src-keyword">lambda</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">_expired</span>(<span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_expired</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">uid</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Session&quot;</span>, <span class="py-src-variable">uid</span>, <span class="py-src-string">&quot;has expired.&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">uid</span>)
+
+<span class="py-src-variable">rootResource</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">rootResource</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;logme&quot;</span>, <span class="py-src-variable">ExpirationLogger</span>())
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">rootResource</span>)
+<span class="py-src-variable">factory</span>.<span class="py-src-variable">sessionFactory</span> = <span class="py-src-variable">ShortSession</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8080</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Since <code>Site</code> customization is required, this example can't be
+rpy-based, so it brings back the manual <code>reactor.listenTCP</code>
+and <code>reactor.run</code> calls. Run it and visit <code>/logme</code> to see
+it in action. Keep visiting it to keep your session active. Stop visiting it for
+five seconds to see your session expiration message.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/session-store.html b/doc/web/howto/web-in-60/session-store.html
new file mode 100644
index 0000000..2f25dcd
--- /dev/null
+++ b/doc/web/howto/web-in-60/session-store.html
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Storing Objects in the Session</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Storing Objects in the Session</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>This example shows you how you can persist objects across requests in the
+session object.</p>
+
+<p>As was discussed <a href="session-basics.html" shape="rect">previously</a>, instances
+of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Session.html" title="twisted.web.server.Session">Session</a></code> last as long as
+the notional session itself does. Each time <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Request.getSession.html" title="twisted.web.server.Request.getSession">Request.getSession</a></code> is called, if the session
+for the request is still active, then the same <code>Session</code> instance is
+returned as was returned previously. Because of this, <code>Session</code>
+instances can be used to keep other objects around for as long as the session
+exists.</p>
+
+<p>It's easier to demonstrate how this works than explain it, so here's an
+example:</p>
+
+<pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; from zope.interface import Interface, Attribute, implements
+&gt;&gt;&gt; from twisted.python.components import registerAdapter
+&gt;&gt;&gt; from twisted.web.server import Session
+&gt;&gt;&gt; class ICounter(Interface):
+... value = Attribute(&quot;An int value which counts up once per page view.&quot;)
+...
+&gt;&gt;&gt; class Counter(object):
+... implements(ICounter)
+... def __init__(self, session):
+... self.value = 0
+...
+&gt;&gt;&gt; registerAdapter(Counter, Session, ICounter)
+&gt;&gt;&gt; ses = Session(None, None)
+&gt;&gt;&gt; data = ICounter(ses)
+&gt;&gt;&gt; print data
+&lt;__main__.Counter object at 0x8d535ec&gt;
+&gt;&gt;&gt; print data is ICounter(ses)
+True
+&gt;&gt;&gt;
+</pre>
+
+<p><i>What?</i>, I hear you say.</p>
+
+<p>What's shown in this example is the interface and adaption-based
+API which <code>Session</code> exposes for persisting state. There are
+several critical pieces interacting here:</p>
+
+<ul>
+ <li><code>ICounter</code> is an interface which serves several purposes. Like
+ all interfaces, it documents the API of some class of objects (in this case,
+ just the <code>value</code> attribute). It also serves as a key into what is
+ basically a dictionary within the session object: the interface is used to
+ store or retrieve a value on the session (the <code>Counter</code> instance,
+ in this case).</li>
+ <li><code>Counter</code> is the class which actually holds the session data in
+ this example. It implements <code>ICounter</code> (again, mostly for
+ documentation purposes). It also has a <code>value</code> attribute, as the
+ interface declared.</li>
+ <li>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.components.registerAdapter.html" title="twisted.python.components.registerAdapter">registerAdapter</a></code> call sets up the
+ relationship between its three arguments so that adaption will do what we
+ want in this case.</li>
+ <li>Adaption is performed by the expression <code>ICounter(ses)</code>. This
+ is read as : adapt <code>ses</code> to <code>ICounter</code>. Because
+ of the <code>registerAdapter</code> call, it is roughly equivalent
+ to <code>Counter(ses)</code>. However (because of certain
+ things <code>Session</code> does), it also saves the <code>Counter</code>
+ instance created so that it will be returned the next time this adaption is
+ done. This is why the last statement produces <code>True</code>.</li>
+</ul>
+
+<p>If you're still not clear on some of the details there, don't worry about it
+and just remember this: <code>ICounter(ses)</code> gives you an object you can
+persist state on. It can be as much or as little state as you want, and you can
+use as few or as many different <code>Interface</code> classes as you want on a
+single <code>Session</code> instance.</p>
+
+<p>With those conceptual dependencies out of the way, it's a very short step to
+actually getting persistent state into a Twisted Web application. Here's an
+example which implements a simple counter, re-using the definitions from the
+example above:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CounterResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">session</span> = <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>()
+ <span class="py-src-variable">counter</span> = <span class="py-src-variable">ICounter</span>(<span class="py-src-variable">session</span>)
+ <span class="py-src-variable">counter</span>.<span class="py-src-variable">value</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Visit #%d for you!&quot;</span> % (<span class="py-src-variable">counter</span>.<span class="py-src-variable">value</span>,)
+</pre>
+
+<p>Pretty simple from this side, eh? All this does is
+use <code>Request.getSession</code> and the adaption from above, plus some
+integer math to give you a session-based visit counter.</p>
+
+<p>Here's the complete source for an <a href="rpy-scripts.html" shape="rect">rpy script</a>
+based on this example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-variable">cache</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">Attribute</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">components</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">registerAdapter</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Session</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ICounter</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-variable">value</span> = <span class="py-src-variable">Attribute</span>(<span class="py-src-string">&quot;An int value which counts up once per page view.&quot;</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">ICounter</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">session</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">value</span> = <span class="py-src-number">0</span>
+
+<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">Counter</span>, <span class="py-src-variable">Session</span>, <span class="py-src-variable">ICounter</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CounterResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">session</span> = <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>()
+ <span class="py-src-variable">counter</span> = <span class="py-src-variable">ICounter</span>(<span class="py-src-variable">session</span>)
+ <span class="py-src-variable">counter</span>.<span class="py-src-variable">value</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Visit #%d for you!&quot;</span> % (<span class="py-src-variable">counter</span>.<span class="py-src-variable">value</span>,)
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">CounterResource</span>()
+</pre>
+
+<p>One more thing to note is the <code>cache()</code> call at the top
+of this example. As with the <a href="http-auth.html" shape="rect">previous
+example</a> where this came up, this rpy script is stateful. This
+time, it's the <code>ICounter</code> definition and
+the <code>registerAdapter</code> call that need to be executed only
+once. If we didn't use <code>cache</code>, every request would define
+a new, different interface named <code>ICounter</code>. Each of these
+would be a different key in the session, so the counter would never
+get past one.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/static-content.html b/doc/web/howto/web-in-60/static-content.html
new file mode 100644
index 0000000..a4f30dd
--- /dev/null
+++ b/doc/web/howto/web-in-60/static-content.html
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Serving Static Content From a Directory</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Serving Static Content From a Directory</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The goal of this example is to show you how to serve static content
+from a filesystem. First, we need to import some objects:</p>
+
+<ul>
+
+<li>
+<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code>, an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IProtocolFactory.html" title="twisted.internet.interfaces.IProtocolFactory">IProtocolFactory</a></code> which
+glues a listening server port (<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.interfaces.IListeningPort.html" title="twisted.internet.interfaces.IListeningPort">IListeningPort</a></code>) to the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.http.HTTPChannel.html" title="twisted.web.http.HTTPChannel">HTTPChannel</a></code>
+implementation:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+</pre>
+</li>
+
+<li>
+<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.static.File.html" title="twisted.web.static.File">File</a></code>, an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.IResource.html" title="twisted.web.resource.IResource">IResource</a></code> which glues
+the HTTP protocol implementation to the filesystem:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+</pre>
+</li>
+
+<li>
+The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code>, which
+drives the whole process, actually accepting TCP connections and
+moving bytes into and out of them:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+</li>
+
+</ul>
+
+Next, we create an instance of the File resource pointed at the
+directory to serve:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">resource</span> = <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/tmp&quot;</span>)
+</pre>
+
+Then we create an instance of the Site factory with that resource:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>)
+</pre>
+
+Now we glue that factory to a TCP port:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8888</span>, <span class="py-src-variable">factory</span>)
+</pre>
+
+Finally, we start the reactor so it can make the program work:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+And that's it. Here's the complete program:
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">File</span>(<span class="py-src-string">'/tmp'</span>)
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8888</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Bonus example! For those times when you don't actually want to
+write a new program, the above implemented functionality is one of the
+things the command line <code>twistd</code> tool can do. In this case,
+the command
+<pre xml:space="preserve">
+twistd -n web --path /tmp
+</pre>
+will accomplish the same thing as the above server. See <a href="../../../core/howto/basics.html" shape="rect">helper programs</a> in the
+Twisted Core documentation for more information on using
+<code>twistd</code>.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/static-dispatch.html b/doc/web/howto/web-in-60/static-dispatch.html
new file mode 100644
index 0000000..3084ce1
--- /dev/null
+++ b/doc/web/howto/web-in-60/static-dispatch.html
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Static URL Dispatch</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Static URL Dispatch</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The goal of this example is to show you how to serve different content at
+different URLs.</p>
+
+<p>The key to understanding how different URLs are handled with the resource
+APIs in Twisted Web is understanding that any URL can be used to address a node
+in a tree. Resources in Twisted Web exist in such a tree, and a request for a
+URL will be responded to by the resource which that URL addresses. The
+addressing scheme considers only the path segments of the URL. Starting with the
+root resource (the one used to construct the <code>Site</code>) and the first
+path segment, a child resource is looked up. As long as there are more path
+segments, this process is repeated using the result of the previous lookup and
+the next path segment. For example, to handle a request
+for <code>&quot;/foo/bar&quot;</code>, first the root's <code>&quot;foo&quot;</code> child is
+retrieved, then that resource's <code>&quot;bar&quot;</code> child is retrieved, then that
+resource is used to create the response.</p>
+
+<p>With that out of the way, let's consider an example that can serve a few
+different resources at a few different URLs.</p>
+
+<p>First things first: we need to import <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code>, the factory for HTTP servers, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>, a convenient base class
+for custom pages, and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code>,
+the object which implements the Twisted main loop. We'll also import <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.static.File.html" title="twisted.web.static.File">File</a></code> to use as the resource at one
+of the example URLs.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+</pre>
+
+<p>Now we create a resource which will correspond to the root of the URL
+hierarchy: all URLs are children of this resource.</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+</pre>
+
+<p>Here comes the interesting part of this example. We're now going to
+create three more resources and attach them to the three
+URLs <code>/foo</code>, <code>/bar</code>, and <code>/baz</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/tmp&quot;</span>))
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;bar&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/lost+found&quot;</span>))
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;baz&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/opt&quot;</span>))
+</pre>
+
+<p>Last, all that's required is to create a <code>Site</code> with the root
+resource, associate it with a listening server port, and start the reactor:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>With this server running, <code>http://localhost:8880/foo</code>
+will serve a listing of files
+from <code>/tmp</code>, <code>http://localhost:8880/bar</code> will
+serve a listing of files from <code>/lost+found</code>,
+and <code>http://localhost:8880/baz</code> will serve a listing of
+files from <code>/opt</code>.</p>
+
+<p>Here's the whole example uninterrupted:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/tmp&quot;</span>))
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;bar&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/lost+found&quot;</span>))
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;baz&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/opt&quot;</span>))
+
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-in-60/wsgi.html b/doc/web/howto/web-in-60/wsgi.html
new file mode 100644
index 0000000..e88084e
--- /dev/null
+++ b/doc/web/howto/web-in-60/wsgi.html
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: WSGI</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">WSGI</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The goal of this example is to show you how to
+use <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.wsgi.WSGIResource.html" title="twisted.web.wsgi.WSGIResource">WSGIResource</a></code>,
+another existing <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code> subclass, to
+serve <a href="http://www.python.org/dev/peps/pep-0333/" shape="rect">WSGI applications</a>
+in a Twisted Web server.</p>
+
+<p>Note that <code>WSGIResource</code> is a multithreaded WSGI container. Like
+any other WSGI container, you can't do anything asynchronous in your WSGI
+applications, even though this is a Twisted WSGI container.</p>
+
+<p>The first new thing in this example is the import
+of <code>WSGIResource</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">wsgi</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">WSGIResource</span>
+</pre>
+
+<p>Nothing too surprising there. We still need one of the other usual suspects,
+too:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<p>You'll see why in a minute. Next, we need a WSGI application. Here's a really
+simple one just to get things going:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">application</span>(<span class="py-src-parameter">environ</span>, <span class="py-src-parameter">start_response</span>):
+ <span class="py-src-variable">start_response</span>(<span class="py-src-string">'200 OK'</span>, [(<span class="py-src-string">'Content-type'</span>, <span class="py-src-string">'text/plain'</span>)])
+ <span class="py-src-keyword">return</span> [<span class="py-src-string">'Hello, world!'</span>]
+</pre>
+
+<p>If this doesn't make sense to you, take a look at one of
+these <a href="http://wsgi.org/wsgi/Learn_WSGI" shape="rect">fine tutorials</a>. Otherwise,
+or once you're done with that, the next step is to create
+a <code>WSGIResource</code> instance, as this is going to be
+another <a href="rpy-scripts.html" shape="rect">rpy script</a> example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">resource</span> = <span class="py-src-variable">WSGIResource</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">getThreadPool</span>(), <span class="py-src-variable">application</span>)
+</pre>
+
+<p>Let's dwell on this line for a minute. The first parameter passed
+to <code>WSGIResource</code> is the reactor. Despite the fact that the
+reactor is global and any code that wants it can always just import it
+(as, in fact, this rpy script simply does itself), passing it around
+as a parameter leaves the door open for certain future possibilities -
+for example, having more than one reactor. There are also testing
+implications. Consider how much easier it is to unit test a function
+that accepts a reactor - perhaps a mock reactor specially constructed
+to make your tests easy to write - rather than importing the real
+global reactor. That's why <code>WSGIResource</code> requires you to
+pass the reactor to it.</p>
+
+<p>The second parameter passed to <code>WSGIResource</code> is
+a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.threadpool.ThreadPool.html" title="twisted.python.threadpool.ThreadPool">ThreadPool</a></code>. <code>WSGIResource</code>
+uses this to actually call the application object passed in to it. To keep this
+example short, we're passing in the reactor's internal threadpool here, letting
+us skip its creation and shutdown-time destruction. For finer control over how
+many WSGI requests are served in parallel, you may want to create your own
+thread pool to use with your <code>WSGIResource</code>, but for simple testing,
+using the reactor's is fine.</p>
+
+<p>The final argument is the application object. This is pretty typical of how
+WSGI containers work.</p>
+
+<p>The example, sans interruption:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">wsgi</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">WSGIResource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">application</span>(<span class="py-src-parameter">environ</span>, <span class="py-src-parameter">start_response</span>):
+ <span class="py-src-variable">start_response</span>(<span class="py-src-string">'200 OK'</span>, [(<span class="py-src-string">'Content-type'</span>, <span class="py-src-string">'text/plain'</span>)])
+ <span class="py-src-keyword">return</span> [<span class="py-src-string">'Hello, world!'</span>]
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">WSGIResource</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">getThreadPool</span>(), <span class="py-src-variable">application</span>)
+</pre>
+
+<p>Up to the point where the <code>WSGIResource</code> instance defined here
+exists in the resource hierarchy, the normal resource traversal rules
+apply: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.getChild.html" title="twisted.web.resource.Resource.getChild">getChild</a></code>
+will be called to handle each segment. Once the <code>WSGIResource</code> is
+encountered, though, that process stops and all further URL handling is the
+responsibility of the WSGI application. This application does nothing with the
+URL, though, so you won't be able to tell that.</p>
+
+<p>Oh, and as was the case with the first static file example, there's also a
+command line option you can use to avoid a lot of this. If you just put the
+above application function, without all of the <code>WSGIResource</code> stuff,
+into a file, say, <code>foo.py</code>, then you can launch a roughly equivalent
+server like this:</p>
+
+<pre class="shell" xml:space="preserve">
+$ twistd -n web --wsgi foo.application
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/web-overview.html b/doc/web/howto/web-overview.html
new file mode 100644
index 0000000..6e70c4b
--- /dev/null
+++ b/doc/web/howto/web-overview.html
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Overview of Twisted Web</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Overview of Twisted Web</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Twisted Web's Structure</a></li><li><a href="#auto2">Resources</a></li><li><a href="#auto3">
+ Web programming with Twisted Web
+ </a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Introduction<a name="auto0"/></h2>
+
+ <p>Twisted Web is a web application server written in pure
+ Python, with APIs at multiple levels of abstraction to
+ facilitate different kinds of web programming.
+ </p>
+
+ <h2>Twisted Web's Structure<a name="auto1"/></h2>
+
+ <p><img src="../img/web-overview.png"/></p>
+
+ <p>When
+ the Web Server receives a request from a Client, it creates
+ a Request object and passes it on to the Resource system.
+ The Resource system dispatches to the appropriate Resource
+ object based on what path was requested by the client. The
+ Resource is asked to render itself, and the result is
+ returned to the client.</p>
+
+ <h2>Resources<a name="auto2"/></h2>
+
+ <p>Resources are the lowest-level abstraction for applications
+ in the Twisted web server. Each Resource is a 1:1 mapping with
+ a path that is requested: you can think of a Resource as a
+ single <q>page</q> to be rendered. The interface for making
+ Resources is very simple; they must have a method named
+ <code>render</code> which takes a single argument, which is the
+ Request object (an instance of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">twisted.web.server.Request</a></code>). This render
+ method must return a string, which will be returned to the web
+ browser making the request. Alternatively, they can return a
+ special constant, <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.NOT_DONE_YET.html" title="twisted.web.server.NOT_DONE_YET">twisted.web.server.NOT_DONE_YET</a></code>, which tells
+ the web server not to close the connection; you must then use
+ <code class="python">request.write(data)</code> to render the
+ page, and call <code class="python">request.finish()</code>
+ whenever you're done.
+ </p>
+
+ <h2>
+ Web programming with Twisted Web
+ <a name="auto3"/></h2>
+
+ <p>
+ Web programmers seeking a higher level abstraction than the Resource system
+ should look at <a href="https://launchpad.net/nevow" shape="rect">Nevow</a>.
+ Nevow is based on ideas previously developed in Twisted, but is now maintained
+ outside of Twisted to easy development and release cycle pressures.
+ </p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/howto/xmlrpc.html b/doc/web/howto/xmlrpc.html
new file mode 100644
index 0000000..4637187
--- /dev/null
+++ b/doc/web/howto/xmlrpc.html
@@ -0,0 +1,651 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Creating XML-RPC Servers and Clients with Twisted</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Creating XML-RPC Servers and Clients with Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Creating a XML-RPC server</a></li><ul><li><a href="#auto2">Using XML-RPC sub-handlers</a></li><li><a href="#auto3">Using your own procedure getter</a></li><li><a href="#auto4">Adding XML-RPC Introspection support</a></li></ul><li><a href="#auto5">SOAP Support</a></li><li><a href="#auto6">Creating an XML-RPC Client</a></li><li><a href="#auto7">Serving SOAP and XML-RPC simultaneously</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p><a href="http://www.xmlrpc.com" shape="rect">XML-RPC</a> is a simple request/reply protocol
+that runs over HTTP. It is simple, easy to implement and supported by most programming
+languages. Twisted's XML-RPC support is implemented using the
+<a href="http://docs.python.org/library/xmlrpclib.html" shape="rect">xmlrpclib</a> library that is
+included with Python 2.2 and later.</p>
+
+<h2>Creating a XML-RPC server<a name="auto1"/></h2>
+
+<p>Making a server is very easy - all you need to do is inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.xmlrpc.XMLRPC.html" title="twisted.web.xmlrpc.XMLRPC">twisted.web.xmlrpc.XMLRPC</a></code>.
+You then create methods beginning with <code>xmlrpc_</code>. The methods'
+arguments determine what arguments it will accept from XML-RPC clients.
+The result is what will be returned to the clients.</p>
+
+<p>Methods published via XML-RPC can return all the basic XML-RPC
+types, such as strings, lists and so on (just return a regular python
+integer, etc). They can also raise exceptions or return Failure instances to indicate an
+error has occurred, or <code>Binary</code>, <code>Boolean</code> or <code>DateTime</code>
+instances (all of these are the same as the respective classes in xmlrpclib. In
+addition, XML-RPC published methods can return <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> instances whose results are one of the above. This allows
+you to return results that can't be calculated immediately, such as database queries.
+See the <a href="../../core/howto/defer.html" shape="rect">Deferred documentation</a> for more
+details.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.xmlrpc.XMLRPC.html" title="twisted.web.xmlrpc.XMLRPC">XMLRPC</a></code> instances
+are Resource objects, and they can thus be published using a Site. The
+following example has two methods published via XML-RPC, <code>add(a,
+b)</code> and <code>echo(x)</code>.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Example</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ An example object to be published.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return all passed args.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">x</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return sum of arguments.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> + <span class="py-src-variable">b</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_fault</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Raise a Fault indicating that the procedure should not be used.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">Fault</span>(<span class="py-src-number">123</span>, <span class="py-src-string">&quot;The fault procedure is faulty.&quot;</span>)
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">Example</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">7080</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>After we run this command, we can connect with a client and send commands
+to the server:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; import xmlrpclib
+&gt;&gt;&gt; s = xmlrpclib.Server('http://localhost:7080/')
+&gt;&gt;&gt; s.echo(&quot;lala&quot;)
+'lala'
+&gt;&gt;&gt; s.add(1, 2)
+3
+&gt;&gt;&gt; s.fault()
+Traceback (most recent call last):
+...
+xmlrpclib.Fault: &lt;Fault 123: 'The fault procedure is faulty.'&gt;
+&gt;&gt;&gt;
+
+</pre>
+
+<p>If the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">Request</a></code> object is
+needed by an <code>xmlrpc_*</code> method, it can be made available using
+the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.xmlrpc.withRequest.html" title="twisted.web.xmlrpc.withRequest">twisted.web.xmlrpc.withRequest</a></code> decorator. When
+using this decorator, the method will be passed the request object as the first
+argument, before any XML-RPC parameters. For example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">xmlrpc</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">XMLRPC</span>, <span class="py-src-variable">withRequest</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Example</span>(<span class="py-src-parameter">XMLRPC</span>):
+ @<span class="py-src-variable">withRequest</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_headerValue</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>, <span class="py-src-parameter">headerName</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">request</span>.<span class="py-src-variable">requestHeaders</span>.<span class="py-src-variable">getRawHeaders</span>(<span class="py-src-variable">headerName</span>)
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">7080</span>, <span class="py-src-variable">Site</span>(<span class="py-src-variable">Example</span>()))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>XML-RPC resources can also be part of a normal Twisted web server, using
+resource scripts. The following is an example of such a resource script:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;What are you talking about, William?&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Quoter</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_quote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">getQuote</span>()
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">Quoter</span>()
+</pre><div class="caption">Source listing - <a href="listings/xmlquote.rpy"><span class="filename">listings/xmlquote.rpy</span></a></div></div>
+
+<h3>Using XML-RPC sub-handlers<a name="auto2"/></h3>
+
+<p>XML-RPC resource can be nested so that one handler calls another if
+a method with a given prefix is called. For example, to add support
+for an XML-RPC method <code>date.time()</code> to
+the <code class="python">Example</code> class, you could do the
+following:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Example</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ An example object to be published.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return all passed args.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">x</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return sum of arguments.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> + <span class="py-src-variable">b</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Date</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Serve the XML-RPC 'time' method.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_time</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return UNIX time.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">Example</span>()
+ <span class="py-src-variable">date</span> = <span class="py-src-variable">Date</span>()
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">putSubHandler</span>(<span class="py-src-string">'date'</span>, <span class="py-src-variable">date</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">7080</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>By default, a period ('.') separates the prefix from the method
+name, but you can use a different character by overriding the <code class="python">XMLRPC.separator</code> data member in your base
+XML-RPC server. XML-RPC servers may be nested to arbitrary depths
+using this method.</p>
+
+<h3>Using your own procedure getter<a name="auto3"/></h3>
+
+<p>Sometimes, you want to implement your own policy of getting the end implementation.
+E.g. just like sub-handlers you want to divide the implementations into separate classes but
+may not want to introduce <code class="python">XMLRPC.separator</code> in the procedure name.
+In such cases just override the <code class="python">lookupProcedure(self, procedurePath)</code>
+method and return the correct callable.
+Raise <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.xmlrpc.NoSuchFunction.html" title="twisted.web.xmlrpc.NoSuchFunction">twisted.web.xmlrpc.NoSuchFunction</a></code> otherwise.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoHandler</span>:
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return all passed args
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">x</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AddHandler</span>:
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return sum of arguments.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> + <span class="py-src-variable">b</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Example</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ An example of using you own policy to fetch the handler
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_addHandler</span> = <span class="py-src-variable">AddHandler</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_echoHandler</span> = <span class="py-src-variable">EchoHandler</span>()
+
+ <span class="py-src-comment">#We keep a dict of all relevant</span>
+ <span class="py-src-comment">#procedure names and callable.</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_procedureToCallable</span> = {
+ <span class="py-src-string">'add'</span>:<span class="py-src-variable">self</span>.<span class="py-src-variable">_addHandler</span>.<span class="py-src-variable">add</span>,
+ <span class="py-src-string">'echo'</span>:<span class="py-src-variable">self</span>.<span class="py-src-variable">_echoHandler</span>.<span class="py-src-variable">echo</span>
+ }
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lookupProcedure</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">procedurePath</span>):
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_procedureToCallable</span>[<span class="py-src-variable">procedurePath</span>]
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">KeyError</span>, <span class="py-src-variable">e</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">NoSuchFunction</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">NOT_FOUND</span>,
+ <span class="py-src-string">&quot;procedure %s not found&quot;</span> % <span class="py-src-variable">procedurePath</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">listProcedures</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Since we override lookupProcedure, its suggested to override
+ listProcedures too.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> [<span class="py-src-string">'add'</span>, <span class="py-src-string">'echo'</span>]
+
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">Example</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">7080</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/xmlrpc-customized.py"><span class="filename">listings/xmlrpc-customized.py</span></a></div></div>
+
+<h3>Adding XML-RPC Introspection support<a name="auto4"/></h3>
+
+<p>XML-RPC has an
+informal <a href="http://tldp.org/HOWTO/XML-RPC-HOWTO/xmlrpc-howto-interfaces.html" shape="rect">Introspection
+API</a> that specifies three methods in a <code>system</code>
+sub-handler which allow a client to query a server about the server's
+API. Adding Introspection support to
+the <code class="python">Example</code> class is easy using
+the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.xmlrpc.XMLRPCIntrospection.html" title="twisted.web.xmlrpc.XMLRPCIntrospection">XMLRPCIntrospection</a></code> class:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Example</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-string">&quot;&quot;&quot;An example object to be published.&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return all passed args.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">x</span>
+
+ <span class="py-src-variable">xmlrpc_echo</span>.<span class="py-src-variable">signature</span> = [[<span class="py-src-string">'string'</span>, <span class="py-src-string">'string'</span>],
+ [<span class="py-src-string">'int'</span>, <span class="py-src-string">'int'</span>],
+ [<span class="py-src-string">'double'</span>, <span class="py-src-string">'double'</span>],
+ [<span class="py-src-string">'array'</span>, <span class="py-src-string">'array'</span>],
+ [<span class="py-src-string">'struct'</span>, <span class="py-src-string">'struct'</span>]]
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return sum of arguments.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> + <span class="py-src-variable">b</span>
+
+ <span class="py-src-variable">xmlrpc_add</span>.<span class="py-src-variable">signature</span> = [[<span class="py-src-string">'int'</span>, <span class="py-src-string">'int'</span>, <span class="py-src-string">'int'</span>],
+ [<span class="py-src-string">'double'</span>, <span class="py-src-string">'double'</span>, <span class="py-src-string">'double'</span>]]
+ <span class="py-src-variable">xmlrpc_add</span>.<span class="py-src-variable">help</span> = <span class="py-src-string">&quot;Add the arguments and return the sum.&quot;</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">Example</span>()
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">addIntrospection</span>(<span class="py-src-variable">r</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">7080</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Note the method attributes <code class="python">help</code>
+and <code class="python">signature</code> which are used by the
+Introspection API methods <code>system.methodHelp</code>
+and <code>system.methodSignature</code> respectively. If
+no <code class="python">help</code> attribute is specified, the
+method's documentation string is used instead.</p>
+
+<h2>SOAP Support<a name="auto5"/></h2>
+
+<p>From the point of view of a Twisted developer, there is little difference
+between XML-RPC support and SOAP support. Here is an example of SOAP usage:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">soap</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;That beverage, sir, is off the hizzy.&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Quoter</span>(<span class="py-src-parameter">soap</span>.<span class="py-src-parameter">SOAPPublisher</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Publish one method, 'quote'.&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">soap_quote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">getQuote</span>()
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">Quoter</span>()
+</pre><div class="caption">Source listing - <a href="listings/soap.rpy"><span class="filename">listings/soap.rpy</span></a></div></div>
+
+
+<h2>Creating an XML-RPC Client<a name="auto6"/></h2>
+
+<p>XML-RPC clients in Twisted are meant to look as something which will be
+familiar either to <code>xmlrpclib</code> or to Perspective Broker users,
+taking features from both, as appropriate. There are two major deviations
+from the <code>xmlrpclib</code> way which should be noted:</p>
+
+<ol>
+<li>No implicit <code>/RPC2</code>. If the services uses this path for the
+ XML-RPC calls, then it will have to be given explicitly.</li>
+<li>No magic <code>__getattr__</code>: calls must be made by an explicit
+ <code>callRemote</code>.</li>
+</ol>
+
+<p>The interface Twisted presents to XML-RPC client is that of a proxy
+object: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.xmlrpc.Proxy.html" title="twisted.web.xmlrpc.Proxy">twisted.web.xmlrpc.Proxy</a></code>. The
+constructor for the object receives a URL: it must be an HTTP or HTTPS
+URL. When an XML-RPC service is described, the URL to that service
+will be given there.</p>
+
+<p>Having a proxy object, one can just call the <code>callRemote</code> method,
+which accepts a method name and a variable argument list (but no named
+arguments, as these are not supported by XML-RPC). It returns a deferred,
+which will be called back with the result. If there is any error, at any
+level, the errback will be called. The exception will be the relevant Twisted
+error in the case of a problem with the underlying connection (for example,
+a timeout), <code>IOError</code> containing the status and message in the case
+of a non-200 status or a <code>xmlrpclib.Fault</code> in the case of an
+XML-RPC level problem.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">xmlrpc</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Proxy</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">repr</span>(<span class="py-src-variable">value</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printError</span>(<span class="py-src-parameter">error</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'error'</span>, <span class="py-src-variable">error</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">proxy</span> = <span class="py-src-variable">Proxy</span>(<span class="py-src-string">'http://advogato.org/XMLRPC'</span>)
+<span class="py-src-variable">proxy</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'test.sumprod'</span>, <span class="py-src-number">3</span>, <span class="py-src-number">5</span>).<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">printValue</span>, <span class="py-src-variable">printError</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>prints:</p>
+
+<pre xml:space="preserve">
+[8, 15]
+</pre>
+
+<h2>Serving SOAP and XML-RPC simultaneously<a name="auto7"/></h2>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.xmlrpc.XMLRPC.html" title="twisted.web.xmlrpc.XMLRPC">twisted.web.xmlrpc.XMLRPC</a></code> and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.soap.SOAPPublisher.html" title="twisted.web.soap.SOAPPublisher">twisted.web.soap.SOAPPublisher</a></code> are both <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>s. So, to serve both XML-RPC and
+SOAP in the one web server, you can use the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.IResource.putChild.html" title="twisted.web.resource.IResource.putChild">putChild</a></code> method of Resource.</p>
+
+<p>The following example uses an empty <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">resource.Resource</a></code> as the root resource for
+a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code>, and then
+adds <code>/RPC2</code> and <code>/SOAP</code> paths to it.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">soap</span>, <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Victory to the burgeois, you capitalist swine!&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">XMLRPCQuoter</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_quote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">getQuote</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SOAPQuoter</span>(<span class="py-src-parameter">soap</span>.<span class="py-src-parameter">SOAPPublisher</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">soap_quote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">getQuote</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">root</span> = <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>()
+ <span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">XMLRPCQuoter</span>())
+ <span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'SOAP'</span>, <span class="py-src-variable">SOAPQuoter</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">7080</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/xmlAndSoapQuote.py"><span class="filename">listings/xmlAndSoapQuote.py</span></a></div></div>
+
+<p>Refer to <a href="using-twistedweb.html#development" shape="rect">Twisted Web
+Development</a> for more details about Resources.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/web/img/controller.png b/doc/web/img/controller.png
new file mode 100644
index 0000000..462268e
--- /dev/null
+++ b/doc/web/img/controller.png
Binary files differ
diff --git a/doc/web/img/livepage.png b/doc/web/img/livepage.png
new file mode 100644
index 0000000..131d8d7
--- /dev/null
+++ b/doc/web/img/livepage.png
Binary files differ
diff --git a/doc/web/img/model.png b/doc/web/img/model.png
new file mode 100644
index 0000000..30058b0
--- /dev/null
+++ b/doc/web/img/model.png
Binary files differ
diff --git a/doc/web/img/plone_root_model.png b/doc/web/img/plone_root_model.png
new file mode 100644
index 0000000..9f0f385
--- /dev/null
+++ b/doc/web/img/plone_root_model.png
Binary files differ
diff --git a/doc/web/img/view.png b/doc/web/img/view.png
new file mode 100644
index 0000000..5fdbc05
--- /dev/null
+++ b/doc/web/img/view.png
Binary files differ
diff --git a/doc/web/img/web-overview.dia b/doc/web/img/web-overview.dia
new file mode 100644
index 0000000..4bc9be9
--- /dev/null
+++ b/doc/web/img/web-overview.dia
Binary files differ
diff --git a/doc/web/img/web-overview.png b/doc/web/img/web-overview.png
new file mode 100644
index 0000000..3eff9bd
--- /dev/null
+++ b/doc/web/img/web-overview.png
Binary files differ
diff --git a/doc/web/img/web-process.png b/doc/web/img/web-process.png
new file mode 100644
index 0000000..4f5ab66
--- /dev/null
+++ b/doc/web/img/web-process.png
Binary files differ
diff --git a/doc/web/img/web-process.svg b/doc/web/img/web-process.svg
new file mode 100644
index 0000000..54a3850
--- /dev/null
+++ b/doc/web/img/web-process.svg
@@ -0,0 +1,594 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="900"
+ height="650"
+ id="svg2"
+ inkscape:version="0.47 r22583"
+ sodipodi:docname="web-process.svg"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/web-process.png"
+ inkscape:export-xdpi="53.88356"
+ inkscape:export-ydpi="53.88356">
+ <metadata
+ id="metadata2876">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1207"
+ inkscape:window-height="788"
+ id="namedview2874"
+ showgrid="false"
+ showguides="false"
+ inkscape:zoom="0.80439329"
+ inkscape:cx="568.43722"
+ inkscape:cy="418.14159"
+ inkscape:window-x="344"
+ inkscape:window-y="33"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg2"
+ inkscape:guide-bbox="true"
+ showborder="true"
+ borderlayer="false"
+ inkscape:showpageshadow="false">
+ <sodipodi:guide
+ position="653.14257,-346.44227"
+ orientation="0,744.09448"
+ id="guide7662" />
+ <sodipodi:guide
+ position="1397.237,-346.44227"
+ orientation="-1052.3622,0"
+ id="guide7664" />
+ <sodipodi:guide
+ position="1397.237,705.91993"
+ orientation="0,-744.09448"
+ id="guide7666" />
+ <sodipodi:guide
+ position="653.14257,705.91993"
+ orientation="1052.3622,0"
+ id="guide7668" />
+ <sodipodi:guide
+ position="653.14257,-346.44227"
+ orientation="0,744.09448"
+ id="guide7670" />
+ <sodipodi:guide
+ position="1397.237,-346.44227"
+ orientation="-1052.3622,0"
+ id="guide7672" />
+ <sodipodi:guide
+ position="1397.237,705.91993"
+ orientation="0,-744.09448"
+ id="guide7674" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lend"
+ style="overflow:visible;">
+ <path
+ id="path4316"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.8) rotate(180) translate(12.5,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lend"
+ style="overflow:visible">
+ <path
+ id="path4334"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(-1.1,0,0,-1.1,-1.1,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lstart"
+ style="overflow:visible">
+ <path
+ id="path4331"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(1.1,0,0,1.1,1.1,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mstart"
+ style="overflow:visible">
+ <path
+ id="path4319"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(0.4,0,0,0.4,4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ id="path4337"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart"
+ style="overflow:visible">
+ <path
+ id="path4313"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective2878" />
+ <inkscape:perspective
+ id="perspective3681"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3695"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3743"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3783"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3816"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3952"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3979"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective4032"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective4297"
+ inkscape:persp3d-origin="250 : 166.66667 : 1"
+ inkscape:vp_z="500 : 250 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 250 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective4787"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective5576"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective5603"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective6025"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective6865"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective7806"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ </defs>
+ <g
+ id="layer1"
+ transform="matrix(0.93010115,0,0,0.93010115,250.13373,-60.976065)"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001">
+ <text
+ x="234.28571"
+ y="132.36218"
+ id="text2818"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans Bold"
+ sodipodi:linespacing="125%"><tspan
+ x="234.28571"
+ y="132.36218"
+ id="tspan2820">Twisted Web</tspan></text>
+ </g>
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:2.70000005;stroke-miterlimit:4;stroke-dasharray:none"
+ id="rect2884"
+ width="570.77271"
+ height="231.87642"
+ x="301.6925"
+ y="115.17715"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="424.94702"
+ y="95.464104"
+ id="text3701"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan3703"
+ x="424.94702"
+ y="95.464104">http://example.com/foo/bar/baz</tspan></text>
+ <rect
+ style="fill:#b8ffb8;fill-opacity:1;stroke:#000000;stroke-width:1.1715759;stroke-miterlimit:4;stroke-dasharray:none"
+ id="rect3705"
+ width="95.166527"
+ height="213.02367"
+ x="312.14368"
+ y="123.7507"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="358.15546"
+ y="199.9054"
+ id="text3707"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan3709"
+ x="358.15546"
+ y="199.9054">H</tspan><tspan
+ sodipodi:role="line"
+ x="358.15546"
+ y="224.9054"
+ id="tspan3711">T</tspan><tspan
+ sodipodi:role="line"
+ x="358.15546"
+ y="249.9054"
+ id="tspan3713">T</tspan><tspan
+ sodipodi:role="line"
+ x="358.15546"
+ y="274.9054"
+ id="tspan3715">P</tspan></text>
+ <rect
+ style="fill:#c8ebeb;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none"
+ id="rect3789"
+ width="445.31635"
+ height="211.98299"
+ x="416.99731"
+ y="124.27103"
+ ry="46.083252"
+ rx="0"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="478.34595"
+ y="224.9054"
+ id="text3822"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan3824"
+ x="478.34595"
+ y="224.9054">Object</tspan><tspan
+ sodipodi:role="line"
+ x="478.34595"
+ y="247.4054"
+ id="tspan3826">Publisher</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="675.25073"
+ y="210.61969"
+ id="text3828"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan3830"
+ x="675.25073"
+ y="210.61969">getChild(&quot;foo&quot;)</tspan><tspan
+ sodipodi:role="line"
+ x="675.25073"
+ y="233.11969"
+ id="tspan3832"> getChild(&quot;bar&quot;)</tspan><tspan
+ sodipodi:role="line"
+ x="675.25073"
+ y="255.61969"
+ id="tspan3834"> getChild(&quot;baz&quot;)</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:144px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#3f3f3f;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="542.63159"
+ y="292.76254"
+ id="text3842"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan3844"
+ x="542.63159"
+ y="292.76254">↻</tspan></text>
+ <g
+ id="g4300"
+ transform="matrix(0.57696609,0,0,0.51806079,460.16063,443.16138)"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001">
+ <path
+ sodipodi:nodetypes="cccccccccccccccccc"
+ id="path835"
+ d="m 163.26321,64.542987 c 3.597,0 35.974,-23.982698 88.737,-26.381098 71.948,-8.394 101.927,38.372498 101.927,38.372498 -2.398,5.9957 49.164,-37.173398 79.143,-33.575998 32.377,4.7966 45.568,19.186098 41.97,49.164598 -2.398,19.186703 -22.784,27.580703 -27.58,37.173703 2.398,4.796 28.779,20.386 28.779,62.356 0,41.97 -26.381,64.753 -25.182,64.753 1.199,0 26.381,41.97 7.195,67.152 -19.186,25.182 -73.148,1.2 -81.542,-3.597 -8.394,2.398 -13.32007,47.47397 -119.17674,52.27097 -87.33582,0 -124.24926,-33.08497 -136.24026,-37.88197 -14.39,9.593 -73.147796,22.785 -92.334196,-13.19 -15.58881,-45.567 8.3941,-56.36 13.1906,-64.754 -4.7965,-10.792 -20.3854,-28.779 -17.9871,-62.355 0,-43.17 10.7923,-64.754 20.3855,-71.949 -4.7966,-9.593 -23.9829,-47.965903 17.987,-71.948401 69.550196,-17.9872 99.529196,15.588898 100.728196,14.389698 z"
+ style="fill:#000000;fill-opacity:0.5;fill-rule:evenodd;stroke:none" />
+ <path
+ sodipodi:nodetypes="cccccccccccccccccc"
+ id="path598"
+ d="m 158.287,57.5163 c 3.597,0 35.974,-23.9827 88.737,-26.3811 71.948,-8.394 101.927,38.3725 101.927,38.3725 -2.398,5.9957 49.164,-37.1734 79.143,-33.576 32.377,4.7966 45.568,19.1861 41.97,49.1646 -2.398,19.1867 -22.784,27.5807 -27.58,37.1737 2.398,4.796 28.779,20.386 28.779,62.356 0,41.97 -26.381,64.753 -25.182,64.753 1.199,0 26.381,41.97 7.195,67.152 -19.186,25.182 -73.148,1.2 -81.542,-3.597 -8.394,2.398 -13.32007,47.47397 -119.17674,52.27097 C 165.22144,365.20497 128.308,332.12 116.317,327.323 101.927,336.916 43.1692,350.108 23.9828,314.133 8.39399,268.566 32.3769,257.773 37.1734,249.379 32.3769,238.587 16.788,220.6 19.1863,187.024 c 0,-43.17 10.7923,-64.754 20.3855,-71.949 C 34.7752,105.482 15.5889,67.1091 57.5588,43.1266 127.109,25.1394 157.088,58.7155 158.287,57.5163 z"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.77096152;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:5;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Lstart)"
+ d="m 357.21235,348.82073 0,81.41873 0,0 0,0"
+ id="path4762"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="329.22418"
+ y="453.06482"
+ id="text4793"
+ sodipodi:linespacing="100%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan4795"
+ x="329.22418"
+ y="453.06482">Finish</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.77699995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 357.21235,529.36939 0,-67.3435"
+ id="path5551"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <image
+ y="528.07153"
+ x="355.14368"
+ id="image5605"
+ height="2"
+ width="117"
+ xlink:href="file:///Users/thijstriemstra/Desktop/inkscape_pasted_image_20091210_211422.png"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="693.24731"
+ y="396.24796"
+ id="text5609"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan5611"
+ x="693.24731"
+ y="396.24796">Render</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.63258982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 774.86673,265.48334 c 0,143.1595 0,143.1595 0,143.1595 l 0,0"
+ id="path5613"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.98251843;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 676.93043,409.22247 c 98.723,0 98.723,0 98.723,0"
+ id="path5615"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.74416637;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lend)"
+ d="m 677.69453,408.55589 0,59.90598"
+ id="path5807"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:100%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="601.7937"
+ y="536.74927"
+ id="text6031"
+ sodipodi:linespacing="100%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan6033"
+ x="601.7937"
+ y="536.74927">Templating</tspan><tspan
+ sodipodi:role="line"
+ x="601.7937"
+ y="560.74927"
+ id="tspan6035">System</tspan></text>
+ <rect
+ style="fill:#e2ddd8;fill-opacity:1;stroke:#000000;stroke-width:2.27699995;stroke-miterlimit:4;stroke-dasharray:none"
+ id="rect6058"
+ width="136.48538"
+ height="210.41495"
+ x="33.1385"
+ y="127.60599"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="229.00862"
+ y="183.2704"
+ id="text6060"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan6062"
+ x="229.00862"
+ y="183.2704">Request</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="244.9366"
+ y="270.70905"
+ id="text6064"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan6066"
+ x="244.9366"
+ y="270.70905">Response</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:100%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="99.959412"
+ y="236.50031"
+ id="text6068"
+ sodipodi:linespacing="100%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan6070"
+ x="99.959412"
+ y="236.50031">Browser</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.76616168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Lstart)"
+ d="m 298.85972,194.46803 c -128.91105,0 -128.91105,0 -128.91105,0"
+ id="path6871"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.77699995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lstart)"
+ d="m 301.95575,280.22714 -130.10029,0 0,0 0,0 0,0 0,0"
+ id="path7812" />
+</svg>
diff --git a/doc/web/img/web-session.png b/doc/web/img/web-session.png
new file mode 100644
index 0000000..c4aeba7
--- /dev/null
+++ b/doc/web/img/web-session.png
Binary files differ
diff --git a/doc/web/img/web-widgets.dia b/doc/web/img/web-widgets.dia
new file mode 100644
index 0000000..6c6b37a
--- /dev/null
+++ b/doc/web/img/web-widgets.dia
Binary files differ
diff --git a/doc/web/img/web-widgets.png b/doc/web/img/web-widgets.png
new file mode 100644
index 0000000..6fef28e
--- /dev/null
+++ b/doc/web/img/web-widgets.png
Binary files differ
diff --git a/doc/web/index.html b/doc/web/index.html
new file mode 100644
index 0000000..c058873
--- /dev/null
+++ b/doc/web/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Web Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Web Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Web to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Web</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/words/examples/cursesclient.py b/doc/words/examples/cursesclient.py
new file mode 100644
index 0000000..c739d47
--- /dev/null
+++ b/doc/words/examples/cursesclient.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This is an example of integrating curses with the twisted underlying
+select loop. Most of what is in this is insignificant -- the main piece
+of interest is the 'CursesStdIO' class.
+
+This class acts as file-descriptor 0, and is scheduled with the twisted
+select loop via reactor.addReader (once the curses class extends it
+of course). When there is input waiting doRead is called, and any
+input-oriented curses calls (ie. getch()) should be executed within this
+block.
+
+Remember to call nodelay(1) in curses, to make getch() non-blocking.
+"""
+
+# System Imports
+import curses, time, traceback, sys
+import curses.wrapper
+
+# Twisted imports
+from twisted.internet import reactor
+from twisted.internet.protocol import ClientFactory
+from twisted.words.protocols.irc import IRCClient
+from twisted.python import log
+
+class TextTooLongError(Exception):
+ pass
+
+class CursesStdIO:
+ """fake fd to be registered as a reader with the twisted reactor.
+ Curses classes needing input should extend this"""
+
+ def fileno(self):
+ """ We want to select on FD 0 """
+ return 0
+
+ def doRead(self):
+ """called when input is ready"""
+
+ def logPrefix(self): return 'CursesClient'
+
+
+class IRC(IRCClient):
+
+ """ A protocol object for IRC """
+
+ nickname = "testcurses"
+
+ def __init__(self, screenObj):
+ # screenObj should be 'stdscr' or a curses window/pad object
+ self.screenObj = screenObj
+ # for testing (hacky way around initial bad design for this example) :)
+ self.screenObj.irc = self
+
+ def lineReceived(self, line):
+ """ When receiving a line, add it to the output buffer """
+ self.screenObj.addLine(line)
+
+ def connectionMade(self):
+ IRCClient.connectionMade(self)
+ self.screenObj.addLine("* CONNECTED")
+
+ def clientConnectionLost(self, connection, reason):
+ pass
+
+
+class IRCFactory(ClientFactory):
+
+ """
+ Factory used for creating IRC protocol objects
+ """
+
+ protocol = IRC
+
+ def __init__(self, screenObj):
+ self.irc = self.protocol(screenObj)
+
+ def buildProtocol(self, addr=None):
+ return self.irc
+
+ def clientConnectionLost(self, conn, reason):
+ pass
+
+
+class Screen(CursesStdIO):
+ def __init__(self, stdscr):
+ self.timer = 0
+ self.statusText = "TEST CURSES APP -"
+ self.searchText = ''
+ self.stdscr = stdscr
+
+ # set screen attributes
+ self.stdscr.nodelay(1) # this is used to make input calls non-blocking
+ curses.cbreak()
+ self.stdscr.keypad(1)
+ curses.curs_set(0) # no annoying mouse cursor
+
+ self.rows, self.cols = self.stdscr.getmaxyx()
+ self.lines = []
+
+ curses.start_color()
+
+ # create color pair's 1 and 2
+ curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
+ curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK)
+
+ self.paintStatus(self.statusText)
+
+ def connectionLost(self, reason):
+ self.close()
+
+ def addLine(self, text):
+ """ add a line to the internal list of lines"""
+
+ self.lines.append(text)
+ self.redisplayLines()
+
+ def redisplayLines(self):
+ """ method for redisplaying lines
+ based on internal list of lines """
+
+ self.stdscr.clear()
+ self.paintStatus(self.statusText)
+ i = 0
+ index = len(self.lines) - 1
+ while i < (self.rows - 3) and index >= 0:
+ self.stdscr.addstr(self.rows - 3 - i, 0, self.lines[index],
+ curses.color_pair(2))
+ i = i + 1
+ index = index - 1
+ self.stdscr.refresh()
+
+ def paintStatus(self, text):
+ if len(text) > self.cols: raise TextTooLongError
+ self.stdscr.addstr(self.rows-2,0,text + ' ' * (self.cols-len(text)),
+ curses.color_pair(1))
+ # move cursor to input line
+ self.stdscr.move(self.rows-1, self.cols-1)
+
+ def doRead(self):
+ """ Input is ready! """
+ curses.noecho()
+ self.timer = self.timer + 1
+ c = self.stdscr.getch() # read a character
+
+ if c == curses.KEY_BACKSPACE:
+ self.searchText = self.searchText[:-1]
+
+ elif c == curses.KEY_ENTER or c == 10:
+ self.addLine(self.searchText)
+ # for testing too
+ try: self.irc.sendLine(self.searchText)
+ except: pass
+ self.stdscr.refresh()
+ self.searchText = ''
+
+ else:
+ if len(self.searchText) == self.cols-2: return
+ self.searchText = self.searchText + chr(c)
+
+ self.stdscr.addstr(self.rows-1, 0,
+ self.searchText + (' ' * (
+ self.cols-len(self.searchText)-2)))
+ self.stdscr.move(self.rows-1, len(self.searchText))
+ self.paintStatus(self.statusText + ' %d' % len(self.searchText))
+ self.stdscr.refresh()
+
+ def close(self):
+ """ clean up """
+
+ curses.nocbreak()
+ self.stdscr.keypad(0)
+ curses.echo()
+ curses.endwin()
+
+if __name__ == '__main__':
+ stdscr = curses.initscr() # initialize curses
+ screen = Screen(stdscr) # create Screen object
+ stdscr.refresh()
+ ircFactory = IRCFactory(screen)
+ reactor.addReader(screen) # add screen object as a reader to the reactor
+ reactor.connectTCP("irc.freenode.net",6667,ircFactory) # connect to IRC
+ reactor.run() # have fun!
+ screen.close()
diff --git a/doc/words/examples/index.html b/doc/words/examples/index.html
new file mode 100644
index 0000000..3a6bfb1
--- /dev/null
+++ b/doc/words/examples/index.html
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Words code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Words code examples</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <ul>
+ <li><a href="ircLogBot.py" shape="rect">ircLogBot.py</a> - connects to an IRC server and logs all messages</li>
+ <li><a href="minchat.py" shape="rect">minchat.py</a> - log bot using twisted.im</li>
+ <li><a href="msn_example.py" shape="rect">msn_example.py</a></li>
+ <li><a href="oscardemo.py" shape="rect">oscardemo.py</a></li>
+ <li><a href="jabber_client.py" shape="rect">jabber_client.py</a></li>
+ <li><a href="pb_client.py" shape="rect">pb_client.py</a></li>
+ <li><a href="xmpp_client.py" shape="rect">xmpp_client.py</a></li>
+ <li><a href="cursesclient.py" shape="rect">cursesclient.py</a> - trivial curses-based IRC client</li>
+ </ul>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/words/examples/ircLogBot.py b/doc/words/examples/ircLogBot.py
new file mode 100644
index 0000000..18cc7d6
--- /dev/null
+++ b/doc/words/examples/ircLogBot.py
@@ -0,0 +1,158 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example IRC log bot - logs a channel's events to a file.
+
+If someone says the bot's name in the channel followed by a ':',
+e.g.
+
+ <foo> logbot: hello!
+
+the bot will reply:
+
+ <logbot> foo: I am a log bot
+
+Run this script with two arguments, the channel name the bot should
+connect to, and file to log to, e.g.:
+
+ $ python ircLogBot.py test test.log
+
+will log channel #test to the file 'test.log'.
+"""
+
+
+# twisted imports
+from twisted.words.protocols import irc
+from twisted.internet import reactor, protocol
+from twisted.python import log
+
+# system imports
+import time, sys
+
+
+class MessageLogger:
+ """
+ An independent logger class (because separation of application
+ and protocol logic is a good thing).
+ """
+ def __init__(self, file):
+ self.file = file
+
+ def log(self, message):
+ """Write a message to the file."""
+ timestamp = time.strftime("[%H:%M:%S]", time.localtime(time.time()))
+ self.file.write('%s %s\n' % (timestamp, message))
+ self.file.flush()
+
+ def close(self):
+ self.file.close()
+
+
+class LogBot(irc.IRCClient):
+ """A logging IRC bot."""
+
+ nickname = "twistedbot"
+
+ def connectionMade(self):
+ irc.IRCClient.connectionMade(self)
+ self.logger = MessageLogger(open(self.factory.filename, "a"))
+ self.logger.log("[connected at %s]" %
+ time.asctime(time.localtime(time.time())))
+
+ def connectionLost(self, reason):
+ irc.IRCClient.connectionLost(self, reason)
+ self.logger.log("[disconnected at %s]" %
+ time.asctime(time.localtime(time.time())))
+ self.logger.close()
+
+
+ # callbacks for events
+
+ def signedOn(self):
+ """Called when bot has succesfully signed on to server."""
+ self.join(self.factory.channel)
+
+ def joined(self, channel):
+ """This will get called when the bot joins the channel."""
+ self.logger.log("[I have joined %s]" % channel)
+
+ def privmsg(self, user, channel, msg):
+ """This will get called when the bot receives a message."""
+ user = user.split('!', 1)[0]
+ self.logger.log("<%s> %s" % (user, msg))
+
+ # Check to see if they're sending me a private message
+ if channel == self.nickname:
+ msg = "It isn't nice to whisper! Play nice with the group."
+ self.msg(user, msg)
+ return
+
+ # Otherwise check to see if it is a message directed at me
+ if msg.startswith(self.nickname + ":"):
+ msg = "%s: I am a log bot" % user
+ self.msg(channel, msg)
+ self.logger.log("<%s> %s" % (self.nickname, msg))
+
+ def action(self, user, channel, msg):
+ """This will get called when the bot sees someone do an action."""
+ user = user.split('!', 1)[0]
+ self.logger.log("* %s %s" % (user, msg))
+
+ # irc callbacks
+
+ def irc_NICK(self, prefix, params):
+ """Called when an IRC user changes their nickname."""
+ old_nick = prefix.split('!')[0]
+ new_nick = params[0]
+ self.logger.log("%s is now known as %s" % (old_nick, new_nick))
+
+
+ # For fun, override the method that determines how a nickname is changed on
+ # collisions. The default method appends an underscore.
+ def alterCollidedNick(self, nickname):
+ """
+ Generate an altered version of a nickname that caused a collision in an
+ effort to create an unused related name for subsequent registration.
+ """
+ return nickname + '^'
+
+
+
+class LogBotFactory(protocol.ClientFactory):
+ """A factory for LogBots.
+
+ A new protocol instance will be created each time we connect to the server.
+ """
+
+ def __init__(self, channel, filename):
+ self.channel = channel
+ self.filename = filename
+
+ def buildProtocol(self, addr):
+ p = LogBot()
+ p.factory = self
+ return p
+
+ def clientConnectionLost(self, connector, reason):
+ """If we get disconnected, reconnect to server."""
+ connector.connect()
+
+ def clientConnectionFailed(self, connector, reason):
+ print "connection failed:", reason
+ reactor.stop()
+
+
+if __name__ == '__main__':
+ # initialize logging
+ log.startLogging(sys.stdout)
+
+ # create factory protocol and application
+ f = LogBotFactory(sys.argv[1], sys.argv[2])
+
+ # connect factory to this host and port
+ reactor.connectTCP("irc.freenode.net", 6667, f)
+
+ # run bot
+ reactor.run()
diff --git a/doc/words/examples/jabber_client.py b/doc/words/examples/jabber_client.py
new file mode 100644
index 0000000..c4d6f5f
--- /dev/null
+++ b/doc/words/examples/jabber_client.py
@@ -0,0 +1,29 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# Originally written by Darryl Vandorp
+# http://randomthoughts.vandorp.ca/
+
+from twisted.words.protocols.jabber import client, jid
+from twisted.words.xish import domish
+from twisted.internet import reactor
+
+def authd(xmlstream):
+ print "authenticated"
+
+ presence = domish.Element(('jabber:client','presence'))
+ xmlstream.send(presence)
+
+ xmlstream.addObserver('/message', debug)
+ xmlstream.addObserver('/presence', debug)
+ xmlstream.addObserver('/iq', debug)
+
+def debug(elem):
+ print elem.toXml().encode('utf-8')
+ print "="*20
+
+myJid = jid.JID('username@server.jabber/twisted_words')
+factory = client.basicClientFactory(myJid, 'password')
+factory.addBootstrap('//event/stream/authd',authd)
+reactor.connectTCP('server.jabber',5222,factory)
+reactor.run()
diff --git a/doc/words/examples/minchat.py b/doc/words/examples/minchat.py
new file mode 100644
index 0000000..5ea2d0c
--- /dev/null
+++ b/doc/words/examples/minchat.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+A very simple twisted.words.im-based logbot.
+
+To run the script:
+$ python minchat.py
+"""
+
+from twisted.words.im import basechat, baseaccount, ircsupport
+
+
+# A list of account objects. We might as well create them at runtime, this is
+# supposed to be a Minimalist Implementation, after all.
+
+accounts = [
+ ircsupport.IRCAccount("IRC", 1,
+ "Tooty", # nickname
+ "", # passwd
+ "irc.freenode.net", # irc server
+ 6667, # port
+ "#twisted", # comma-seperated list of channels
+ )
+]
+
+
+class AccountManager (baseaccount.AccountManager):
+ """
+ This class is a minimal implementation of the Acccount Manager.
+
+ Most implementations will show some screen that lets the user add and
+ remove accounts, but we're not quite that sophisticated.
+ """
+
+ def __init__(self):
+
+ self.chatui = MinChat()
+
+ if len(accounts) == 0:
+ print "You have defined no accounts."
+ for acct in accounts:
+ acct.logOn(self.chatui)
+
+
+class MinConversation(basechat.Conversation):
+ """
+ This class is a minimal implementation of the abstract Conversation class.
+
+ This is all you need to override to receive one-on-one messages.
+ """
+ def show(self):
+ """
+ If you don't have a GUI, this is a no-op.
+ """
+ pass
+
+ def hide(self):
+ """
+ If you don't have a GUI, this is a no-op.
+ """
+ pass
+
+ def showMessage(self, text, metadata=None):
+ print "<%s> %s" % (self.person.name, text)
+
+ def contactChangedNick(self, person, newnick):
+ basechat.Conversation.contactChangedNick(self, person, newnick)
+ print "-!- %s is now known as %s" % (person.name, newnick)
+
+
+class MinGroupConversation(basechat.GroupConversation):
+ """
+ This class is a minimal implementation of the abstract GroupConversation class.
+
+ This is all you need to override to listen in on a group conversaion.
+ """
+ def show(self):
+ """
+ If you don't have a GUI, this is a no-op.
+ """
+ pass
+
+ def hide(self):
+ """
+ If you don't have a GUI, this is a no-op.
+ """
+ pass
+
+ def showGroupMessage(self, sender, text, metadata=None):
+ print "<%s/%s> %s" % (sender, self.group.name, text)
+
+ def setTopic(self, topic, author):
+ print "-!- %s set the topic of %s to: %s" % (author,
+ self.group.name, topic)
+
+ def memberJoined(self, member):
+ basechat.GroupConversation.memberJoined(self, member)
+ print "-!- %s joined %s" % (member, self.group.name)
+
+ def memberChangedNick(self, oldnick, newnick):
+ basechat.GroupConversation.memberChangedNick(self, oldnick, newnick)
+ print "-!- %s is now known as %s in %s" % (oldnick, newnick,
+ self.group.name)
+
+ def memberLeft(self, member):
+ basechat.GroupConversation.memberLeft(self, member)
+ print "-!- %s left %s" % (member, self.group.name)
+
+
+class MinChat(basechat.ChatUI):
+ """
+ This class is a minimal implementation of the abstract ChatUI class.
+
+ There are only two methods that need overriding - and of those two,
+ the only change that needs to be made is the default value of the Class
+ parameter.
+ """
+
+ def getGroupConversation(self, group, Class=MinGroupConversation,
+ stayHidden=0):
+
+ return basechat.ChatUI.getGroupConversation(self, group, Class,
+ stayHidden)
+
+ def getConversation(self, person, Class=MinConversation,
+ stayHidden=0):
+
+ return basechat.ChatUI.getConversation(self, person, Class, stayHidden)
+
+
+if __name__ == "__main__":
+ from twisted.internet import reactor
+
+ AccountManager()
+
+ reactor.run()
diff --git a/doc/words/examples/msn_example.py b/doc/words/examples/msn_example.py
new file mode 100644
index 0000000..8e4d648
--- /dev/null
+++ b/doc/words/examples/msn_example.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# Twisted Imports
+from twisted.internet import reactor
+from twisted.internet.protocol import ClientFactory
+from twisted.words.protocols import msn
+from twisted.python import log
+
+# System Imports
+import sys, getpass
+
+"""
+This example connects to the MSN chat service and
+prints out information about all the users on your
+contact list (both online and offline).
+
+The main aim of this example is to demonstrate
+the connection process.
+
+@author Samuel Jordan
+"""
+
+
+def _createNotificationFac():
+ fac = msn.NotificationFactory()
+ fac.userHandle = USER_HANDLE
+ fac.password = PASSWORD
+ fac.protocol = Notification
+ return fac
+
+class Dispatch(msn.DispatchClient):
+
+ def __init__(self):
+ msn.DispatchClient.__init__(self)
+ self.userHandle = USER_HANDLE
+
+ def gotNotificationReferral(self, host, port):
+ self.transport.loseConnection()
+ reactor.connectTCP(host, port, _createNotificationFac())
+
+class Notification(msn.NotificationClient):
+
+ def loginFailure(self, message):
+ print 'Login failure:', message
+
+ def listSynchronized(self, *args):
+ contactList = self.factory.contacts
+ print 'Contact list has been synchronized, number of contacts = %s' % len(contactList.getContacts())
+ for contact in contactList.getContacts().values():
+ print 'Contact: %s' % (contact.screenName,)
+ print ' email: %s' % (contact.userHandle,)
+ print ' groups:'
+ for group in contact.groups:
+ print ' - %s' % contactList.groups[group]
+ print
+
+if __name__ == '__main__':
+ USER_HANDLE = raw_input("Email (passport): ")
+ PASSWORD = getpass.getpass()
+ log.startLogging(sys.stdout)
+ _dummy_fac = ClientFactory()
+ _dummy_fac.protocol = Dispatch
+ reactor.connectTCP('messenger.hotmail.com', 1863, _dummy_fac)
+ reactor.run()
diff --git a/doc/words/examples/oscardemo.py b/doc/words/examples/oscardemo.py
new file mode 100755
index 0000000..ec59328
--- /dev/null
+++ b/doc/words/examples/oscardemo.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.words.protocols import oscar
+from twisted.internet import protocol, reactor
+import getpass
+
+SN = raw_input('Username: ') # replace this with a screenname
+PASS = getpass.getpass('Password: ')# replace this with a password
+if SN[0].isdigit():
+ icqMode = 1
+ hostport = ('login.icq.com', 5238)
+else:
+ hostport = ('login.oscar.aol.com', 5190)
+ icqMode = 0
+
+class B(oscar.BOSConnection):
+ capabilities = [oscar.CAP_CHAT]
+ def initDone(self):
+ self.requestSelfInfo().addCallback(self.gotSelfInfo)
+ self.requestSSI().addCallback(self.gotBuddyList)
+ def gotSelfInfo(self, user):
+ print user.__dict__
+ self.name = user.name
+ def gotBuddyList(self, l):
+ print l
+ self.activateSSI()
+ self.setProfile("""this is a test of the current twisted.oscar code.<br>
+current features:<br>
+* send me a message, and you should get it back.<br>
+* invite me to a chat room. i'll repeat what people say. say 'leave' and i'll go.<br>
+* also, i hang out in '%s Chat'. join that, i'll repeat what you say there.<br>
+* try warning me. just try it.<br>
+<br>
+if any of those features don't work, tell paul (Z3Penguin). thanks."""%SN)
+ self.setIdleTime(0)
+ self.clientReady()
+ self.createChat('%s Chat'%SN).addCallback(self.createdRoom)
+ def createdRoom(self, (exchange, fullName, instance)):
+ print 'created room',exchange, fullName, instance
+ self.joinChat(exchange, fullName, instance).addCallback(self.chatJoined)
+ def updateBuddy(self, user):
+ print user
+ def offlineBuddy(self, user):
+ print 'offline', user.name
+ def receiveMessage(self, user, multiparts, flags):
+ print user.name, multiparts, flags
+ self.getAway(user.name).addCallback(self.gotAway, user.name)
+ if multiparts[0][0].find('away')!=-1:
+ self.setAway('I am away from my computer right now.')
+ elif multiparts[0][0].find('back')!=-1:
+ self.setAway(None)
+ if self.awayMessage:
+ self.sendMessage(user.name,'<html><font color="#0000ff">'+self.awayMessage,autoResponse=1)
+ else:
+ self.lastUser = user.name
+ self.sendMessage(user.name, multiparts, wantAck = 1, autoResponse = (self.awayMessage!=None)).addCallback( \
+ self.messageAck)
+ def messageAck(self, (username, message)):
+ print 'message sent to %s acked' % username
+ def gotAway(self, away, user):
+ if away != None:
+ print 'got away for',user,':',away
+ def receiveWarning(self, newLevel, user):
+ print 'got warning from', hasattr(user,'name') and user.name or None
+ print 'new warning level', newLevel
+ if not user:
+ #username = self.lastUser
+ return
+ else:
+ username = user.name
+ self.warnUser(username).addCallback(self.warnedUser, username)
+ def warnedUser(self, oldLevel, newLevel, username):
+ self.sendMessage(username,'muahaha :-p')
+ def receiveChatInvite(self, user, message, exchange, fullName, instance, shortName, inviteTime):
+ print 'chat invite from',user.name,'for room',shortName,'with message:',message
+ self.joinChat(exchange, fullName, instance).addCallback(self.chatJoined)
+ def chatJoined(self, chat):
+ print 'joined chat room', chat.name
+ print 'members:',map(lambda x:x.name,chat.members)
+ def chatReceiveMessage(self, chat, user, message):
+ print 'message to',chat.name,'from',user.name,':',message
+ if user.name!=self.name: chat.sendMessage(user.name+': '+message)
+ if message.find('leave')!=-1 and chat.name!='%s Chat'%SN: chat.leaveChat()
+ def chatMemberJoined(self, chat, member):
+ print member.name,'joined',chat.name
+ def chatMemberLeft(self, chat, member):
+ print member.name,'left',chat.name
+ print 'current members',map(lambda x:x.name,chat.members)
+ if chat.name!="%s Chat"%SN and len(chat.members)==1:
+ print 'leaving', chat.name
+ chat.leaveChat()
+
+class OA(oscar.OscarAuthenticator):
+ BOSClass = B
+
+protocol.ClientCreator(reactor, OA, SN, PASS, icq=icqMode).connectTCP(*hostport)
+reactor.run()
diff --git a/doc/words/examples/pb_client.py b/doc/words/examples/pb_client.py
new file mode 100644
index 0000000..128890c
--- /dev/null
+++ b/doc/words/examples/pb_client.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Simple PB Words client demo
+
+This connects to a server (host/port specified by argv[1]/argv[2]),
+authenticates with a username and password (given by argv[3] and argv[4]),
+joins a group (argv[5]) sends a simple message, leaves the group, and quits
+the server.
+"""
+
+import sys
+from twisted.python import log
+from twisted.cred import credentials
+from twisted.words import service
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class DemoMind(service.PBMind):
+ """An utterly pointless PBMind subclass.
+
+ This notices messages received and prints them to stdout. Since
+ the bot never stays in a channel very long, it is exceedingly
+ unlikely this will ever do anything interesting.
+ """
+ def remote_receive(self, sender, recipient, message):
+ print 'Woop', sender, recipient, message
+
+def quitServer(ignored):
+ """Quit succeeded, shut down the reactor.
+ """
+ reactor.stop()
+
+def leftGroup(ignored, avatar):
+ """Left the group successfully, quit the server.
+ """
+ q = avatar.quit()
+ q.addCallback(quitServer)
+ return q
+
+def sentMessage(ignored, group, avatar):
+ """Sent the message successfully, leave the group.
+ """
+ l = group.leave()
+ l.addCallback(leftGroup, avatar)
+ return l
+
+def joinedGroup(group, avatar):
+ """Joined the group successfully, send a stupid message.
+ """
+ s = group.send({"text": "Hello, monkeys"})
+ s.addCallback(sentMessage, group, avatar)
+ return s
+
+def loggedIn(avatar, group):
+ """Logged in successfully, join a group.
+ """
+ j = avatar.join(group)
+ j.addCallback(joinedGroup, avatar)
+ return j
+
+def errorOccurred(err):
+ """Something went awry, log it and shutdown.
+ """
+ log.err(err)
+ try:
+ reactor.stop()
+ except RuntimeError:
+ pass
+
+def run(host, port, username, password, group):
+ """Create a mind and factory and set things in motion.
+ """
+ m = DemoMind()
+ f = pb.PBClientFactory()
+ f.unsafeTracebacks = True
+ l = f.login(credentials.UsernamePassword(username, password), m)
+ l.addCallback(loggedIn, group)
+ l.addErrback(errorOccurred)
+ reactor.connectTCP(host, int(port), f)
+
+def main():
+ """
+ Set up logging, have the real main function run, and start the reactor.
+ """
+ if len(sys.argv) != 6:
+ raise SystemExit("Usage: %s host port username password group" % (sys.argv[0],))
+ log.startLogging(sys.stdout)
+
+ host, port, username, password, group = sys.argv[1:]
+ port = int(port)
+ username = username.decode(sys.stdin.encoding)
+ group = group.decode(sys.stdin.encoding)
+
+ reactor.callWhenRunning(run, host, port, username, password, group)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/words/examples/xmpp_client.py b/doc/words/examples/xmpp_client.py
new file mode 100644
index 0000000..a99dc68
--- /dev/null
+++ b/doc/words/examples/xmpp_client.py
@@ -0,0 +1,82 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+from twisted.internet import reactor
+from twisted.names.srvconnect import SRVConnector
+from twisted.words.xish import domish
+from twisted.words.protocols.jabber import xmlstream, client, jid
+
+
+class XMPPClientConnector(SRVConnector):
+ def __init__(self, reactor, domain, factory):
+ SRVConnector.__init__(self, reactor, 'xmpp-client', domain, factory)
+
+
+ def pickServer(self):
+ host, port = SRVConnector.pickServer(self)
+
+ if not self.servers and not self.orderedServers:
+ # no SRV record, fall back..
+ port = 5222
+
+ return host, port
+
+
+
+class Client(object):
+ def __init__(self, client_jid, secret):
+ f = client.XMPPClientFactory(client_jid, secret)
+ f.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT, self.connected)
+ f.addBootstrap(xmlstream.STREAM_END_EVENT, self.disconnected)
+ f.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self.authenticated)
+ f.addBootstrap(xmlstream.INIT_FAILED_EVENT, self.init_failed)
+ connector = XMPPClientConnector(reactor, client_jid.host, f)
+ connector.connect()
+
+
+ def rawDataIn(self, buf):
+ print "RECV: %s" % unicode(buf, 'utf-8').encode('ascii', 'replace')
+
+
+ def rawDataOut(self, buf):
+ print "SEND: %s" % unicode(buf, 'utf-8').encode('ascii', 'replace')
+
+
+ def connected(self, xs):
+ print 'Connected.'
+
+ self.xmlstream = xs
+
+ # Log all traffic
+ xs.rawDataInFn = self.rawDataIn
+ xs.rawDataOutFn = self.rawDataOut
+
+
+ def disconnected(self, xs):
+ print 'Disconnected.'
+
+ reactor.stop()
+
+
+ def authenticated(self, xs):
+ print "Authenticated."
+
+ presence = domish.Element((None, 'presence'))
+ xs.send(presence)
+
+ reactor.callLater(5, xs.sendFooter)
+
+
+ def init_failed(self, failure):
+ print "Initialization failed."
+ print failure
+
+ self.xmlstream.sendFooter()
+
+
+client_jid = jid.JID(sys.argv[1])
+secret = sys.argv[2]
+c = Client(client_jid, secret)
+
+reactor.run()
diff --git a/doc/words/howto/im.html b/doc/words/howto/im.html
new file mode 100644
index 0000000..0efb089
--- /dev/null
+++ b/doc/words/howto/im.html
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Overview of Twisted IM</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Overview of Twisted IM</h1>
+ <div class="toc"><ol><li><a href="#auto0">Code flow</a></li><ul><li><a href="#auto1">AccountManager</a></li><li><a href="#auto2">ChatUI</a></li><li><a href="#auto3">Conversation and GroupConversation</a></li><li><a href="#auto4">Accounts</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+ <p>Twisted IM (Instance Messenger) is a multi-protocol chat
+ framework, based on the Twisted framework we've all come to know
+ and love. It's fairly simple and extensible in two directions -
+ it's pretty easy to add new protocols, and it's also quite easy
+ to add new front-ends.</p>
+
+ <h2>Code flow<a name="auto0"/></h2>
+
+ <h3>AccountManager<a name="auto1"/></h3>
+ <p>The control flow starts at the relevant subclass of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.words.im.baseaccount.AccountManager.html" title="twisted.words.im.baseaccount.AccountManager">baseaccount.AccountManager</a></code>.
+ The AccountManager is responsible for, well, managing accounts
+ - remembering what accounts are available, their
+ settings, adding and removal of accounts, and making accounts
+ log on at startup.</p>
+
+ <p>This would be a good place to start your interface, load a
+ list of accounts from disk and tell them to login. Most of the
+ method names in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.words.im.baseaccount.AccountManager.html" title="twisted.words.im.baseaccount.AccountManager">AccountManager</a></code>
+ are pretty self-explanatory, and your subclass can override
+ whatever it wants, but you <em>need</em> to override <code class="python">__init__</code>. Something like
+ this:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p>...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">chatui</span> = ... <span class="py-src-comment"># Your subclass of basechat.ChatUI</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">accounts</span> = ... <span class="py-src-comment"># Load account list</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">a</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">accounts</span>:
+ <span class="py-src-variable">a</span>.<span class="py-src-variable">logOn</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">chatui</span>)
+</pre>
+
+ <h3>ChatUI<a name="auto2"/></h3>
+ <p>Account objects talk to the user via a subclass of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.words.im.basechat.ChatUI.html" title="twisted.words.im.basechat.ChatUI">basechat.ChatUI</a></code>.
+ This class keeps track of all the various conversations that
+ are currently active, so that when an account receives and
+ incoming message, it can put that message in its correct
+ context.</p>
+
+ <p>How much of this class you need to override depends on what
+ you need to do. You will need to override
+ <code>getConversation</code> (a one-on-one conversation, like
+ an IRC DCC chat) and <code>getGroupConversation</code> (a
+ multiple user conversation, like an IRC channel). You might
+ want to override <code>getGroup</code> and
+ <code>getPerson</code>.</p>
+
+ <p>The main problem with the default versions of the above
+ routines is that they take a parameter, <code>Class</code>,
+ which defaults to an abstract implementation of that class -
+ for example, <code>getConversation</code> has a
+ <code>Class</code> parameter that defaults to <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.words.im.basechat.Conversation.html" title="twisted.words.im.basechat.Conversation">basechat.Conversation</a></code> which
+ raises a lot of <code>NotImplementedError</code>s. In your
+ subclass, override the method with a new method whose Class
+ parameter defaults to your own implementation of
+ <code>Conversation</code>, that simply calls the parent
+ class' implementation.</p>
+
+ <h3>Conversation and GroupConversation<a name="auto3"/></h3>
+ <p>These classes are where your interface meets the chat
+ protocol. Chat protocols get a message, find the appropriate
+ <code>Conversation</code> or <code>GroupConversation</code>
+ object, and call its methods when various interesting things
+ happen.</p>
+
+ <p>Override whatever methods you want to get the information
+ you want to display. You must override the <code>hide</code>
+ and <code>show</code> methods, however - they are called
+ frequently and the default implementation raises
+ <code>NotImplementedError</code>.</p>
+
+ <h3>Accounts<a name="auto4"/></h3>
+ <p>An account is an instance of a subclass of <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.words.im.basesupport.AbstractAccount.html" title="twisted.words.im.basesupport.AbstractAccount">basesupport.AbstractAccount</a></code>.
+ For more details and sample code, see the various
+ <code>*support</code> files in <code>twisted.words.im</code>.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/words/howto/index.html b/doc/words/howto/index.html
new file mode 100644
index 0000000..26e9705
--- /dev/null
+++ b/doc/words/howto/index.html
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted IM Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted IM Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+ <li><a href="im.html" shape="rect">Twisted IM</a></li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/doc/words/index.html b/doc/words/index.html
new file mode 100644
index 0000000..711f3e7
--- /dev/null
+++ b/doc/words/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Words Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Words Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Words to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Words</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 12.1.0</span>
+ </body>
+</html> \ No newline at end of file