~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/web/distrib.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.web.test.test_web -*-
 
2
 
 
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
4
# See LICENSE for details.
 
5
 
 
6
 
 
7
"""Distributed web servers.
 
8
 
 
9
This is going to have to be refactored so that argument parsing is done
 
10
by each subprocess and not by the main web server (i.e. GET, POST etc.).
 
11
"""
 
12
 
 
13
# System Imports
 
14
import types, os, copy, string, cStringIO
 
15
if (os.sys.platform != 'win32') and (os.name != 'java'):
 
16
    import pwd
 
17
 
 
18
# Twisted Imports
 
19
from twisted.spread import pb
 
20
from twisted.web import http
 
21
from twisted.python import log
 
22
from twisted.persisted import styles
 
23
from twisted.web.woven import page
 
24
from twisted.internet import address, reactor
 
25
 
 
26
# Sibling Imports
 
27
import resource
 
28
import server
 
29
import error
 
30
import html
 
31
import static
 
32
from server import NOT_DONE_YET
 
33
 
 
34
class _ReferenceableProducerWrapper(pb.Referenceable):
 
35
    def __init__(self, producer):
 
36
        self.producer = producer
 
37
 
 
38
    def remote_resumeProducing(self):
 
39
        self.producer.resumeProducing()
 
40
 
 
41
    def remote_pauseProducing(self):
 
42
        self.producer.pauseProducing()
 
43
 
 
44
    def remote_stopProducing(self):
 
45
        self.producer.stopProducing()
 
46
 
 
47
 
 
48
class Request(pb.RemoteCopy, server.Request):
 
49
    def setCopyableState(self, state):
 
50
        for k in 'host', 'client':
 
51
            tup = state[k]
 
52
            addrdesc = {'INET': 'TCP', 'UNIX': 'UNIX'}[tup[0]]
 
53
            addr = {'TCP': lambda: address.IPv4Address(addrdesc,
 
54
                                                       tup[1], tup[2],
 
55
                                                       _bwHack='INET'),
 
56
                    'UNIX': lambda: address.UNIXAddress(tup[1])}[addrdesc]()
 
57
            state[k] = addr
 
58
        pb.RemoteCopy.setCopyableState(self, state)
 
59
        # Emulate the local request interface --
 
60
        self.content = cStringIO.StringIO(self.content_data)
 
61
        self.write            = self.remote.remoteMethod('write')
 
62
        self.finish           = self.remote.remoteMethod('finish')
 
63
        self.setHeader        = self.remote.remoteMethod('setHeader')
 
64
        self.addCookie        = self.remote.remoteMethod('addCookie')
 
65
        self.setETag          = self.remote.remoteMethod('setETag')
 
66
        self.setResponseCode  = self.remote.remoteMethod('setResponseCode')
 
67
        self.setLastModified  = self.remote.remoteMethod('setLastModified')
 
68
 
 
69
    def registerProducer(self, producer, streaming):
 
70
        self.remote.callRemote("registerProducer",
 
71
                               _ReferenceableProducerWrapper(producer),
 
72
                               streaming).addErrback(self.fail)
 
73
 
 
74
    def unregisterProducer(self):
 
75
        self.remote.callRemote("unregisterProducer").addErrback(self.fail)
 
76
 
 
77
    def fail(self, failure):
 
78
        log.err(failure)
 
79
 
 
80
 
 
81
pb.setCopierForClass(server.Request, Request)
 
82
 
 
83
class Issue:
 
84
    def __init__(self, request):
 
85
        self.request = request
 
86
 
 
87
    def finished(self, result):
 
88
        if result != NOT_DONE_YET:
 
89
            assert isinstance(result, types.StringType),\
 
90
                   "return value not a string"
 
91
            self.request.write(result)
 
92
            self.request.finish()
 
93
 
 
94
    def failed(self, failure):
 
95
        #XXX: Argh. FIXME.
 
96
        failure = str(failure)
 
97
        self.request.write(
 
98
            error.ErrorPage(http.INTERNAL_SERVER_ERROR,
 
99
                            "Server Connection Lost",
 
100
                            "Connection to distributed server lost:" +
 
101
                            html.PRE(failure)).
 
102
            render(self.request))
 
103
        self.request.finish()
 
104
        log.msg(failure)
 
105
 
 
106
 
 
107
class ResourceSubscription(resource.Resource):
 
108
    isLeaf = 1
 
109
    waiting = 0
 
110
    def __init__(self, host, port):
 
111
        resource.Resource.__init__(self)
 
112
        self.host = host
 
113
        self.port = port
 
114
        self.pending = []
 
115
        self.publisher = None
 
116
 
 
117
    def __getstate__(self):
 
118
        """Get persistent state for this ResourceSubscription.
 
119
        """
 
120
        # When I unserialize,
 
121
        state = copy.copy(self.__dict__)
 
122
        # Publisher won't be connected...
 
123
        state['publisher'] = None
 
124
        # I won't be making a connection
 
125
        state['waiting'] = 0
 
126
        # There will be no pending requests.
 
127
        state['pending'] = []
 
128
        return state
 
129
 
 
130
    def connected(self, publisher):
 
131
        """I've connected to a publisher; I'll now send all my requests.
 
132
        """
 
133
        log.msg('connected to publisher')
 
134
        publisher.broker.notifyOnDisconnect(self.booted)
 
135
        self.publisher = publisher
 
136
        self.waiting = 0
 
137
        for request in self.pending:
 
138
            self.render(request)
 
139
        self.pending = []
 
140
 
 
141
    def notConnected(self, msg):
 
142
        """I can't connect to a publisher; I'll now reply to all pending
 
143
        requests.
 
144
        """
 
145
        log.msg("could not connect to distributed web service: %s" % msg)
 
146
        self.waiting = 0
 
147
        self.publisher = None
 
148
        for request in self.pending:
 
149
            request.write("Unable to connect to distributed server.")
 
150
            request.finish()
 
151
        self.pending = []
 
152
 
 
153
    def booted(self):
 
154
        self.notConnected("connection dropped")
 
155
 
 
156
    def render(self, request):
 
157
        """Render this request, from my server.
 
158
 
 
159
        This will always be asynchronous, and therefore return NOT_DONE_YET.
 
160
        It spins off a request to the pb client, and either adds it to the list
 
161
        of pending issues or requests it immediately, depending on if the
 
162
        client is already connected.
 
163
        """
 
164
        if not self.publisher:
 
165
            self.pending.append(request)
 
166
            if not self.waiting:
 
167
                self.waiting = 1
 
168
                bf = pb.PBClientFactory()
 
169
                timeout = 10
 
170
                if self.host == "unix":
 
171
                    reactor.connectUNIX(self.port, bf, timeout)
 
172
                else:
 
173
                    reactor.connectTCP(self.host, self.port, bf, timeout)
 
174
                d = bf.getRootObject()
 
175
                d.addCallbacks(self.connected, self.notConnected)
 
176
 
 
177
        else:
 
178
            i = Issue(request)
 
179
            self.publisher.callRemote('request', request).addCallbacks(i.finished, i.failed)
 
180
        return NOT_DONE_YET
 
181
 
 
182
class ResourcePublisher(pb.Root, styles.Versioned):
 
183
    def __init__(self, site):
 
184
        self.site = site
 
185
 
 
186
    persistenceVersion = 2
 
187
 
 
188
    def upgradeToVersion2(self):
 
189
        self.application.authorizer.removeIdentity("web")
 
190
        del self.application.services[self.serviceName]
 
191
        del self.serviceName
 
192
        del self.application
 
193
        del self.perspectiveName
 
194
 
 
195
    def getPerspectiveNamed(self, name):
 
196
        return self
 
197
 
 
198
    def remote_request(self, request):
 
199
        res = self.site.getResourceFor(request)
 
200
        log.msg( request )
 
201
        return res.render(request)
 
202
 
 
203
class UserDirectory(page.Page):
 
204
    userDirName = 'public_html'
 
205
    userSocketName = '.twistd-web-pb'
 
206
 
 
207
    template = """
 
208
<html>
 
209
    <head>
 
210
    <title>twisted.web.distrib.UserDirectory</title>
 
211
    <style>
 
212
    
 
213
    a
 
214
    {
 
215
        font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
 
216
        color: #369;
 
217
        text-decoration: none;
 
218
    }
 
219
 
 
220
    th
 
221
    {
 
222
        font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
 
223
        font-weight: bold;
 
224
        text-decoration: none;
 
225
        text-align: left;
 
226
    }
 
227
 
 
228
    pre, code
 
229
    {
 
230
        font-family: "Courier New", Courier, monospace;
 
231
    }
 
232
 
 
233
    p, body, td, ol, ul, menu, blockquote, div
 
234
    {
 
235
        font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
 
236
        color: #000;
 
237
    }
 
238
    
 
239
    </style>
 
240
    <base view="Attributes" model="base" />
 
241
    </head>
 
242
 
 
243
    <body>
 
244
    <h1>twisted.web.distrib.UserDirectory</h1>
 
245
 
 
246
    <ul view="List" model="directory">
 
247
            <li pattern="listItem"><a view="Link" /> </li>
 
248
    </ul>
 
249
</body>
 
250
</html>
 
251
    """
 
252
 
 
253
    def wmfactory_base(self, request):
 
254
        return {'href':request.prePathURL()}
 
255
 
 
256
    def wmfactory_directory(self, request):
 
257
        m = []
 
258
        for user in pwd.getpwall():
 
259
            pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, pw_shell \
 
260
                     = user
 
261
            realname = string.split(pw_gecos,',')[0]
 
262
            if not realname:
 
263
                realname = pw_name
 
264
            if os.path.exists(os.path.join(pw_dir, self.userDirName)):
 
265
                m.append({
 
266
                        'href':'%s/'%pw_name,
 
267
                        'text':'%s (file)'%realname
 
268
                })
 
269
            twistdsock = os.path.join(pw_dir, self.userSocketName)
 
270
            if os.path.exists(twistdsock):
 
271
                linknm = '%s.twistd' % pw_name
 
272
                m.append({
 
273
                        'href':'%s/'%linknm,
 
274
                        'text':'%s (twistd)'%realname})
 
275
        return m
 
276
 
 
277
    def getChild(self, name, request):
 
278
        if name == '':
 
279
            return self
 
280
 
 
281
        td = '.twistd'
 
282
 
 
283
        if name[-len(td):] == td:
 
284
            username = name[:-len(td)]
 
285
            sub = 1
 
286
        else:
 
287
            username = name
 
288
            sub = 0
 
289
        try:
 
290
            pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, pw_shell \
 
291
                     = pwd.getpwnam(username)
 
292
        except KeyError:
 
293
            return error.NoResource()
 
294
        if sub:
 
295
            twistdsock = os.path.join(pw_dir, self.userSocketName)
 
296
            rs = ResourceSubscription('unix',twistdsock)
 
297
            self.putChild(name, rs)
 
298
            return rs
 
299
        else:
 
300
            path = os.path.join(pw_dir, self.userDirName)
 
301
            if not os.path.exists(path):
 
302
                return error.NoResource()
 
303
            return static.File(path)