A Twisted Web Tutorial

Twisted Web -- The Tutorial


Sweet (Once More With Feeling, season 6) -- Showtime

Twisted Web


Giles (I Robot -- You Jane, season 1) -- There's a demon in the internet

Short Example: Putting a Server Up

% mktap --uid=33 --gid=33 web --path=/var/www/htdocs --port=80
% sudo twistd -f web.tap

Buffy (Once More, With Feeling, season 6) -- I've got a theory. It doesn't matter.

Setup and Configuration Utilities


Xander (The Harvest, season 1) -- crosses, garlic, stake through the heart

Digression: What are TAPs


Master (The Wish, season 3) -- Behold the technical wonder

mktap


Buffy (Bad Eggs, season 2) -- I'm gonna need a *big* weapon

mktap web: Common Useful Options


Buffy (Bad Eggs, season 2) -- That's probably not gonna be the winning argument, is it?

twistd


Giles (Teacher's Pet, season 1) -- That's all he said? Fork Guy?

What's a Resource?


Sean (Go Fish, season 3) -- You're soakin' in it, bud.

Resource Examples


Xander (As You Were, season 6) -- We have friends, family and demons

Web Development


Xander (Family, season 5) -- That was a tangled web

Custom Processor

from twisted.web import static, twcgi

class PerlScript(twcgi.FilteredScript):
    filter = '/usr/bin/perl' # Points to the perl parser

Tara (Family, season 4) -- There was the front of a camel

Resource Scripting


Tara (Once More, With Feeling, season 6) -- You make me complete

.rpy example

from twisted.web import resource as resourcelib

class MyGreatResource(resourcelib.Resource):
    def render(self, request):
        return "<html>foo</html>"

resource = MyGreatResource()

Willow (Welcome to the Hellmouth, season 1) -- It's probably easy for you.

Alternative Configuration Formats


Ben (The Gift, season 5) -- I wish there was another way

Alternative Configuration Formats -- Python


Buffy (The I In Team, season 4) -- But I've learned that it pays to be flexible in life.

Python Configuration Example

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")))

Willow (The Pack, season 1) -- It's simple, really.

Bannerfish -- A Case Study in Deployment


Xander (Halloween, season 2) -- Let's move out.

Bannerfish -- Standalone tap


Ethan (Halloween, season 2) -- Don't wish to blow my own trumpet, but --

Bannerfish -- Standalone tap (behind reverse proxy)

resource = proxy.ReverseProxyResource('localhost', 81, '/')

Buffy (Halloween, season 2) -- You're sweet. A terrible liar, but sweet.

Bannerfish -- Standalone 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)

Spike (Halloween, season 2) -- Shaking. Terrified. Alone. Lost little lamb.

Bannerfish -- /etc/twisted-web/local.d Drop In

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)

Cordelia (Halloween, season 2) -- Well, I guess you better get them back to their parents.

Bannerfish -- Resource Script

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)

Xander (Innocence, season 2) -- They like to see the big guns.

Bannerfish -- Distributed (Slave)

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)

Bannerfish -- Distributed (Master, Resource Script)

from twisted.web import distrib

resource = distrib.ResourceSubscription('unix',
                        '/var/run/bannerfish')

Oz (Innocence, season 2) -- So, do you guys steal weapons from the Army a lot?

Bannerfish -- Other options


Buffy (Tabula Rasa, season 6) -- I'm like a superhero or something

Bannerfish -- Conclusions


Giles (Killed By Death, season 2) -- Simple enough, but, but

Further Reading


Giles (I Was Made to Love You, Season 5) -- There's an enormous amount of research we should do before -- no I'm lying

Questions?

Vampire Willow (Dopplegangland, season 3): Questions? Comments?

Bonus Slides

Xander (The Dark Age, season 2) -- A bonus day of class plus Cordelia.

Python Configuration -- Hints


Buffy (Phases, season 2) -- Have you dropped any hints?

Python Configuration -- Persistence


Spike (Once More, With Feeling) -- Let me rest in peace

Python Configuration -- Processors


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))

Manny (Doublemeat Palace, season 6) -- It's a meat process

Python Configuration -- Indices


root = static.File("/var/www")
root.indices = ['index.rpy', 'index.html']

Willow (Buffy vs. Dracula, season 5) -- Labelling your amulets and indexing your diaries

Python Configuration -- Virtual Hosts


from twisted.web import vhost
default = static.File("/var/www")
foo = static.File("/var/foo")
root = vhost.NamedVirtualHost(default)
root.addHost('foo.com', foo)

Fritz (I Robot, You Jane, season 1) -- The only reality is virtual.

Python Configuration -- uber example


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')

Buffy (Potential, season 7) -- It was putting a lot of stock in that uber-vamp

Python Configuration -- Splitting With Reverse Proxy

from twisted.web import proxy

root.putChild('foo',
     proxy.ReverseProxyResource('localhost',
                                81, '/foo/'))

Buffy (Once More, With Feeling, season 6 -- So I will walk through the fire

mktap examples


Anya (I Was Made to Love You, season 4) -- You can also see the website I designed for the magic shop

mktap examples (cont'd)


Buffy (Once More, With Feeling, season 6) -- All the twists and bends

mktap examples (alternate formats)


Tara (Seeing Red, season 6) -- It isn't written in any ancient language we could identify.

mktap examples (setting uid)


Buffy (Who Are You?, season 4) -- I would be Buffy

twistd examples


Xander (Teacher's Pet, season 1) -- How come *that* never came up?

Shutting down twistd


Buffy (Prophecy Girl, season 1) -- I don't wanna die.

Shutdown TAPs


Headstone (The Gift, season 5) -- She saved the world. A lot.

twistd and security


Buffy (Dopplegangland, season 3) -- I think it's good to be reliable

Resource Call Examples

site.getChild('foo', request
   ).getChild('bar', request
   ).getChild('baz', request
   ).render(request)

Willow/Tara (Afterlife, Part 2, season 6) -- Child of words, hear thy makers

Resource Call Examples (cont'd)

site.getChild('foo', request
   ).getChild('bar', request
   ).getChild('baz', request
   ).getChild('', request
   ).render(request)

Buffy (Gone, season 6) -- Stop trying to see me.

Distributed Servers -- Theory


Anya (Once More, With Feeling, season 6) -- I've got a theory, it could be bunnies

Distributed Servers -- Manually

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)

Buffy (Some Assembly Required, season 2) -- Men dig up the corpses and the women have the babies.

Distributed Servers -- Manual (cont'd)

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)

Buffy (Welcome to the Hellmouth, season 1) -- Now, we can do this the hard way, or...

Distributed Servers -- Manual (cont'd 2)

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)

Buffy (Welcome to the Hellmouth, season 1) -- ...well, actually there's just the hard way.

Distributed Servers -- User Directory


Master (The Wish, season 3) -- Mass production!

Distributed Servers -- User Directory Server

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)

Richard (Reptile Boy, season 2) -- In his name.

Distributed Servers -- Personal Servers

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)

Giles (Bargaining, season 6) -- It's my personal collection

Debian Configuration


Buffy (Bad Girls, season 3) -- We can help each other.

Debian Configuration -- Usage


Faith (Home Coming, season 3) -- we'll use 'em

Debian Configuration -- Drop In Examples

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)))

Buffy (The Freshman, season 4) -- I just thought I'd drop in

Debian Configuration -- Drop In Examples (cont'd)

from twisted.web import script, static

default.processors['.rpy'] = script.ResourceScript
default.ignoreExt('rpy')

Riley (As You Were, season 6) -- Sorry to just drop in on you

Debian Configuration -- Drop In Examples (cont'd 2)

from twisted.web import vhost

default.putChild('vhost', vhost.VHostMonsterResource())

Sam (As You Were, season 6) -- a hairy night drop into hostile territory

twistedmatrix.com Configuration

...
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)

Xander (The Witch, season 1) -- May all lesser cretins bow before me.

Apache vs. Twisted Web


Willow (Buffy vs. Dracular, season 5) -- I think we've just put our finger on why we're the sidekicks

Apache/Twisted Web Integration


Xander (What's My Line, season 2) -- Angel's our friend! Except I don't like him.

Zope vs. Twisted Web


Willow (Dopplegangland, season 3) -- Competition is natural and healthy

Zope/Twisted Web Integration


Snyder (Dopplegangland, season 3) -- It's a perfect match.

Zope/Twisted Web Integration (cont'd)


Wesley (Dopplegangland, season 3) -- Still a little sloppy, though

Applications Appropriate for Twisted Web


Sweet (Once More, With Feeling) -- Why don't you come and play?

Behind Reverse Proxy


Jenny (I Robot -- You Jane, season 1) -- The divine exists in cyberspace

Rewrite Rules


Spike (What's My Line, season 2) -- Read it again.

Rewrite Rules -- Example


root = static.File("/var/www")
root.putChild("users", distrib.UserDirectory())
root = rewrite.RewriterResource(root, rewrite.tildeToUsers)

Spike (What's My Line, season 2) -- I think it's just enough kill.

websetroot


Manny (DoubleMeat Palace, season 6) -- We have a lot of turnover here

Sample websetroot command lines


Manny (DoubleMeat Palace, season 6) -- You can toss it

init.d


Bob (Zeppo, season 3) -- He hasn't been initiated.

Special Bonus - How to Configure <user>.example.com

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)

Morgan (The Puppet Show, season 1) -- Weird? What d'you mean?

Special Bonus - How to Configure <user>.example.com (cont'd)

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)

Snyder (The Puppet Show, season 1) -- You need to integrate into this school, people.

Using the Twisted Registry


Angel (Helpless, season 3) -- I wanted to keep it safe

Using the Twisted Registry -- example


from twisted.web import distrib

resource = registry.getComponent(distrib.UserDirectory)
if not resource:
    resource = distrib.UserDirectory()
    registry.setComponent(distrib.UserDirectory, resource)

Paul (The Freshman, season 4) -- Do you know where they're distributing the [...] applications?

Using the Twisted Registry -- problems


Anya (Once More, With Feeling, season 6) -- The only trouble is [pause] I'll never tell.

Alternative Configuration Formats -- XML


Buffy (Once More, With Feeling, season 6) -- To fit in in this glittering world.

Alternative Configuration Formats -- XML -- example

<?xml version="1.0"?>

<instance class="twisted.internet.app.Application" reference="1">
  <dictionary>
...
    <string role="key" value="tcpPorts" />
    <list>
      <tuple>
        <int value="80" />
        <instance class="twisted.web.server.Site">
          <dictionary>
...
            <string role="key" value="resource" />
            <instance class="twisted.web.static.File">
              <dictionary>
...
                <string role="key" value="path" />
                <string value="/var/www" />
...
              </dictionary>
            </instance>
...
          </dictionary>
        </instance>
...
      </tuple>
    </list>
...
  </dictionary>
</instance>

Natalie (Teacher's Pet, season 1) -- There's nothing ugly about these creatures

Alternative Configuration Formats -- Source


Willow/Giles/Xander (Primeval, season 4) -- You could never hope to grasp the source

Alternative Configuration Formats -- Source -- Example

app=Ref(1,
  Instance('twisted.internet.app.Application',{
...
      'tcpPorts':[
        (
          80,
          Instance('twisted.web.server.Site',
...
            resource=Instance('twisted.web.static.File',{
...
                'path':'/var/www',
...
          ),
        ],
...
      }))

Tara (Family, season 5) -- You learn her source, and, uh we'll introduce her to her insect reflection

Twisted Web - Beginnings


<glyphAtWork> the http server was so we could say "Web!" if we ever did
              a freshmeat announcement
<glyphAtWork> this makes people excited

Dawn (Get It Done, season 7) -- I think it's an origin myth.

Woven Overview


Razor (Bargaining, season 6) -- A pretty toy