summaryrefslogtreecommitdiff
path: root/pypers/europython05/Quixote-2.0/server/twisted_server.py
blob: 911ec501a9381917180cafe40d540b5b41227910 (plain)
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
#!/usr/bin/env python
"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/twisted_server.py $
$Id: twisted_server.py 25711 2004-12-07 22:52:41Z nascheme $

An HTTP server for Twisted that publishes a Quixote application.
"""

import urllib
from twisted.protocols import http
from twisted.web import server
from twisted.python import threadable
from twisted.internet import reactor
from quixote.http_request import HTTPRequest


class QuixoteFactory(http.HTTPFactory):
    def __init__(self, publisher):
        self.publisher = publisher
        http.HTTPFactory.__init__(self, None)

    def buildProtocol(self, addr):
        protocol = http.HTTPFactory.buildProtocol(self, addr)
        protocol.requestFactory = QuixoteRequest
        return protocol


class QuixoteRequest(server.Request):
    def process(self):
        environ = self.create_environment()
        # this seek is important, it doesn't work without it (it doesn't
        # matter for GETs, but POSTs will not work properly without it.)
        self.content.seek(0, 0)
        qxrequest = HTTPRequest(self.content, environ)
        qxresponse = self.channel.factory.publisher.process_request(qxrequest)
        self.setResponseCode(qxresponse.status_code)
        for name, value in qxresponse.generate_headers():
            if name != 'Set-Cookie':
                self.setHeader(name, value)
        # Cookies get special treatment since it seems Twisted cannot handle
        # multiple Set-Cookie headers.
        for name, attrs in qxresponse.cookies.items():
            attrs = attrs.copy()
            value = attrs.pop('value')
            self.addCookie(name, value, **attrs)
        QuixoteProducer(qxresponse, self)

    def create_environment(self):
        """
        Borrowed heavily from twisted.web.twcgi
        """
        # Twisted doesn't decode the path for us, so let's do it here.
        if '%' in self.path:
            self.path = urllib.unquote(self.path)

        serverName = self.getRequestHostname().split(':')[0]
        env = {"SERVER_SOFTWARE":   server.version,
               "SERVER_NAME":       serverName,
               "GATEWAY_INTERFACE": "CGI/1.1",
               "SERVER_PROTOCOL":   self.clientproto,
               "SERVER_PORT":       str(self.getHost()[2]),
               "REQUEST_METHOD":    self.method,
               "SCRIPT_NAME":       '',
               "SCRIPT_FILENAME":   '',
               "REQUEST_URI":       self.uri,
               "HTTPS":             (self.isSecure() and 'on') or 'off',
               'SERVER_PROTOCOL':   'HTTP/1.1',
        }

        for env_var, header in [('ACCEPT_ENCODING', 'Accept-encoding'),
                                ('CONTENT_TYPE', 'Content-type'),
                                ('HTTP_COOKIE', 'Cookie'),
                                ('HTTP_REFERER', 'Referer'),
                                ('HTTP_USER_AGENT', 'User-agent')]:
            value = self.getHeader(header)
            if value is not None:
                env[env_var] = value

        client = self.getClient()
        if client is not None:
            env['REMOTE_HOST'] = client
        ip = self.getClientIP()
        if ip is not None:
            env['REMOTE_ADDR'] = ip
        _, _, remote_port = self.transport.getPeer()
        env['REMOTE_PORT'] = remote_port
        env["PATH_INFO"] = self.path

        qindex = self.uri.find('?')
        if qindex != -1:
            env['QUERY_STRING'] = self.uri[qindex+1:]
        else:
            env['QUERY_STRING'] = ''

        # Propogate HTTP headers
        for title, header in self.getAllHeaders().items():
            envname = title.replace('-', '_').upper()
            if title not in ('content-type', 'content-length'):
                envname = "HTTP_" + envname
            env[envname] = header

        return env


class QuixoteProducer:
    """
    Produce the Quixote response for twisted.
    """
    def __init__(self, qxresponse, request):
        self.request = request
        self.size = qxresponse.get_content_length()
        self.stream = qxresponse.generate_body_chunks()
        request.registerProducer(self, 0)

    def resumeProducing(self):
        if self.request:
            try:
                chunk = self.stream.next()
            except StopIteration:
                self.request.unregisterProducer()
                self.request.finish()
                self.request = None
            else:
                self.request.write(chunk)

    def pauseProducing(self):
        pass

    def stopProducing(self):
        self.request = None

    synchronized = ['resumeProducing', 'stopProducing']

threadable.synchronize(QuixoteProducer)


def run(create_publisher, host='', port=80):
    """Runs a Twisted HTTP server server that publishes a Quixote
    application."""
    publisher = create_publisher()
    factory = QuixoteFactory(publisher)
    reactor.listenTCP(port, factory, interface=host)
    reactor.run()


if __name__ == '__main__':
    from quixote.server.util import main
    main(run)