~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/web/server.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.web.test.test_web -*-
 
2
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
 
 
6
"""
 
7
This is a web-server which integrates with the twisted.internet
 
8
infrastructure.
 
9
"""
 
10
 
 
11
# System Imports
 
12
 
 
13
import warnings
 
14
import string
 
15
import types
 
16
import copy
 
17
import os
 
18
from urllib import quote
 
19
 
 
20
from zope.interface import implements
 
21
 
 
22
try:
 
23
    from twisted.protocols._c_urlarg import unquote
 
24
except ImportError:
 
25
    from urllib import unquote
 
26
 
 
27
#some useful constants
 
28
NOT_DONE_YET = 1
 
29
 
 
30
# Twisted Imports
 
31
from twisted.spread import pb
 
32
from twisted.internet import defer, address, task
 
33
from twisted.web import iweb, http
 
34
from twisted.python import log, reflect, failure, components
 
35
from twisted import copyright
 
36
from twisted.web import util as webutil, resource
 
37
from twisted.web.error import UnsupportedMethod
 
38
 
 
39
# backwards compatability
 
40
date_time_string = http.datetimeToString
 
41
string_date_time = http.stringToDatetime
 
42
 
 
43
# Support for other methods may be implemented on a per-resource basis.
 
44
supportedMethods = ('GET', 'HEAD', 'POST')
 
45
 
 
46
 
 
47
def _addressToTuple(addr):
 
48
    if isinstance(addr, address.IPv4Address):
 
49
        return ('INET', addr.host, addr.port)
 
50
    elif isinstance(addr, address.UNIXAddress):
 
51
        return ('UNIX', addr.name)
 
52
    else:
 
53
        return tuple(addr)
 
54
 
 
55
class Request(pb.Copyable, http.Request, components.Componentized):
 
56
    implements(iweb.IRequest)
 
57
 
 
58
    site = None
 
59
    appRootURL = None
 
60
    __pychecker__ = 'unusednames=issuer'
 
61
 
 
62
    def __init__(self, *args, **kw):
 
63
        http.Request.__init__(self, *args, **kw)
 
64
        components.Componentized.__init__(self)
 
65
 
 
66
    def getStateToCopyFor(self, issuer):
 
67
        x = self.__dict__.copy()
 
68
        del x['transport']
 
69
        # XXX refactor this attribute out; it's from protocol
 
70
        # del x['server']
 
71
        del x['channel']
 
72
        del x['content']
 
73
        del x['site']
 
74
        self.content.seek(0, 0)
 
75
        x['content_data'] = self.content.read()
 
76
        x['remote'] = pb.ViewPoint(issuer, self)
 
77
 
 
78
        # Address objects aren't jellyable
 
79
        x['host'] = _addressToTuple(x['host'])
 
80
        x['client'] = _addressToTuple(x['client'])
 
81
 
 
82
        # Header objects also aren't jellyable.
 
83
        x['requestHeaders'] = list(x['requestHeaders'].getAllRawHeaders())
 
84
 
 
85
        return x
 
86
 
 
87
    # HTML generation helpers
 
88
 
 
89
    def sibLink(self, name):
 
90
        "Return the text that links to a sibling of the requested resource."
 
91
        if self.postpath:
 
92
            return (len(self.postpath)*"../") + name
 
93
        else:
 
94
            return name
 
95
 
 
96
    def childLink(self, name):
 
97
        "Return the text that links to a child of the requested resource."
 
98
        lpp = len(self.postpath)
 
99
        if lpp > 1:
 
100
            return ((lpp-1)*"../") + name
 
101
        elif lpp == 1:
 
102
            return name
 
103
        else: # lpp == 0
 
104
            if len(self.prepath) and self.prepath[-1]:
 
105
                return self.prepath[-1] + '/' + name
 
106
            else:
 
107
                return name
 
108
 
 
109
    def process(self):
 
110
        "Process a request."
 
111
 
 
112
        # get site from channel
 
113
        self.site = self.channel.site
 
114
 
 
115
        # set various default headers
 
116
        self.setHeader('server', version)
 
117
        self.setHeader('date', http.datetimeToString())
 
118
        self.setHeader('content-type', "text/html")
 
119
 
 
120
        # Resource Identification
 
121
        self.prepath = []
 
122
        self.postpath = map(unquote, string.split(self.path[1:], '/'))
 
123
        try:
 
124
            resrc = self.site.getResourceFor(self)
 
125
            self.render(resrc)
 
126
        except:
 
127
            self.processingFailed(failure.Failure())
 
128
 
 
129
 
 
130
    def render(self, resrc):
 
131
        try:
 
132
            body = resrc.render(self)
 
133
        except UnsupportedMethod, e:
 
134
            allowedMethods = e.allowedMethods
 
135
            if (self.method == "HEAD") and ("GET" in allowedMethods):
 
136
                # We must support HEAD (RFC 2616, 5.1.1).  If the
 
137
                # resource doesn't, fake it by giving the resource
 
138
                # a 'GET' request and then return only the headers,
 
139
                # not the body.
 
140
                log.msg("Using GET to fake a HEAD request for %s" %
 
141
                        (resrc,))
 
142
                self.method = "GET"
 
143
                body = resrc.render(self)
 
144
 
 
145
                if body is NOT_DONE_YET:
 
146
                    log.msg("Tried to fake a HEAD request for %s, but "
 
147
                            "it got away from me." % resrc)
 
148
                    # Oh well, I guess we won't include the content length.
 
149
                else:
 
150
                    self.setHeader('content-length', str(len(body)))
 
151
 
 
152
                self.write('')
 
153
                self.finish()
 
154
                return
 
155
 
 
156
            if self.method in (supportedMethods):
 
157
                # We MUST include an Allow header
 
158
                # (RFC 2616, 10.4.6 and 14.7)
 
159
                self.setHeader('Allow', allowedMethods)
 
160
                s = ('''Your browser approached me (at %(URI)s) with'''
 
161
                     ''' the method "%(method)s".  I only allow'''
 
162
                     ''' the method%(plural)s %(allowed)s here.''' % {
 
163
                    'URI': self.uri,
 
164
                    'method': self.method,
 
165
                    'plural': ((len(allowedMethods) > 1) and 's') or '',
 
166
                    'allowed': string.join(allowedMethods, ', ')
 
167
                    })
 
168
                epage = resource.ErrorPage(http.NOT_ALLOWED,
 
169
                                           "Method Not Allowed", s)
 
170
                body = epage.render(self)
 
171
            else:
 
172
                epage = resource.ErrorPage(http.NOT_IMPLEMENTED, "Huh?",
 
173
                                           "I don't know how to treat a"
 
174
                                           " %s request." % (self.method,))
 
175
                body = epage.render(self)
 
176
        # end except UnsupportedMethod
 
177
 
 
178
        if body == NOT_DONE_YET:
 
179
            return
 
180
        if type(body) is not types.StringType:
 
181
            body = resource.ErrorPage(
 
182
                http.INTERNAL_SERVER_ERROR,
 
183
                "Request did not return a string",
 
184
                "Request: " + html.PRE(reflect.safe_repr(self)) + "<br />" +
 
185
                "Resource: " + html.PRE(reflect.safe_repr(resrc)) + "<br />" +
 
186
                "Value: " + html.PRE(reflect.safe_repr(body))).render(self)
 
187
 
 
188
        if self.method == "HEAD":
 
189
            if len(body) > 0:
 
190
                # This is a Bad Thing (RFC 2616, 9.4)
 
191
                log.msg("Warning: HEAD request %s for resource %s is"
 
192
                        " returning a message body."
 
193
                        "  I think I'll eat it."
 
194
                        % (self, resrc))
 
195
                self.setHeader('content-length', str(len(body)))
 
196
            self.write('')
 
197
        else:
 
198
            self.setHeader('content-length', str(len(body)))
 
199
            self.write(body)
 
200
        self.finish()
 
201
 
 
202
    def processingFailed(self, reason):
 
203
        log.err(reason)
 
204
        if self.site.displayTracebacks:
 
205
            body = ("<html><head><title>web.Server Traceback (most recent call last)</title></head>"
 
206
                    "<body><b>web.Server Traceback (most recent call last):</b>\n\n"
 
207
                    "%s\n\n</body></html>\n"
 
208
                    % webutil.formatFailure(reason))
 
209
        else:
 
210
            body = ("<html><head><title>Processing Failed</title></head><body>"
 
211
                  "<b>Processing Failed</b></body></html>")
 
212
 
 
213
        self.setResponseCode(http.INTERNAL_SERVER_ERROR)
 
214
        self.setHeader('content-type',"text/html")
 
215
        self.setHeader('content-length', str(len(body)))
 
216
        self.write(body)
 
217
        self.finish()
 
218
        return reason
 
219
 
 
220
    def view_write(self, issuer, data):
 
221
        """Remote version of write; same interface.
 
222
        """
 
223
        self.write(data)
 
224
 
 
225
    def view_finish(self, issuer):
 
226
        """Remote version of finish; same interface.
 
227
        """
 
228
        self.finish()
 
229
 
 
230
    def view_addCookie(self, issuer, k, v, **kwargs):
 
231
        """Remote version of addCookie; same interface.
 
232
        """
 
233
        self.addCookie(k, v, **kwargs)
 
234
 
 
235
    def view_setHeader(self, issuer, k, v):
 
236
        """Remote version of setHeader; same interface.
 
237
        """
 
238
        self.setHeader(k, v)
 
239
 
 
240
    def view_setLastModified(self, issuer, when):
 
241
        """Remote version of setLastModified; same interface.
 
242
        """
 
243
        self.setLastModified(when)
 
244
 
 
245
    def view_setETag(self, issuer, tag):
 
246
        """Remote version of setETag; same interface.
 
247
        """
 
248
        self.setETag(tag)
 
249
 
 
250
    def view_setResponseCode(self, issuer, code):
 
251
        """Remote version of setResponseCode; same interface.
 
252
        """
 
253
        self.setResponseCode(code)
 
254
 
 
255
    def view_registerProducer(self, issuer, producer, streaming):
 
256
        """Remote version of registerProducer; same interface.
 
257
        (requires a remote producer.)
 
258
        """
 
259
        self.registerProducer(_RemoteProducerWrapper(producer), streaming)
 
260
 
 
261
    def view_unregisterProducer(self, issuer):
 
262
        self.unregisterProducer()
 
263
 
 
264
    ### these calls remain local
 
265
 
 
266
    session = None
 
267
 
 
268
    def getSession(self, sessionInterface = None):
 
269
        # Session management
 
270
        if not self.session:
 
271
            cookiename = string.join(['TWISTED_SESSION'] + self.sitepath, "_")
 
272
            sessionCookie = self.getCookie(cookiename)
 
273
            if sessionCookie:
 
274
                try:
 
275
                    self.session = self.site.getSession(sessionCookie)
 
276
                except KeyError:
 
277
                    pass
 
278
            # if it still hasn't been set, fix it up.
 
279
            if not self.session:
 
280
                self.session = self.site.makeSession()
 
281
                self.addCookie(cookiename, self.session.uid, path='/')
 
282
        self.session.touch()
 
283
        if sessionInterface:
 
284
            return self.session.getComponent(sessionInterface)
 
285
        return self.session
 
286
 
 
287
    def _prePathURL(self, prepath):
 
288
        port = self.getHost().port
 
289
        if self.isSecure():
 
290
            default = 443
 
291
        else:
 
292
            default = 80
 
293
        if port == default:
 
294
            hostport = ''
 
295
        else:
 
296
            hostport = ':%d' % port
 
297
        return 'http%s://%s%s/%s' % (
 
298
            self.isSecure() and 's' or '',
 
299
            self.getRequestHostname(),
 
300
            hostport,
 
301
            '/'.join([quote(segment, safe='') for segment in prepath]))
 
302
 
 
303
    def prePathURL(self):
 
304
        return self._prePathURL(self.prepath)
 
305
 
 
306
    def URLPath(self):
 
307
        from twisted.python import urlpath
 
308
        return urlpath.URLPath.fromRequest(self)
 
309
 
 
310
    def rememberRootURL(self):
 
311
        """
 
312
        Remember the currently-processed part of the URL for later
 
313
        recalling.
 
314
        """
 
315
        url = self._prePathURL(self.prepath[:-1])
 
316
        self.appRootURL = url
 
317
 
 
318
    def getRootURL(self):
 
319
        """
 
320
        Get a previously-remembered URL.
 
321
        """
 
322
        return self.appRootURL
 
323
 
 
324
 
 
325
class _RemoteProducerWrapper:
 
326
    def __init__(self, remote):
 
327
        self.resumeProducing = remote.remoteMethod("resumeProducing")
 
328
        self.pauseProducing = remote.remoteMethod("pauseProducing")
 
329
        self.stopProducing = remote.remoteMethod("stopProducing")
 
330
 
 
331
 
 
332
class Session(components.Componentized):
 
333
    """
 
334
    A user's session with a system.
 
335
 
 
336
    This utility class contains no functionality, but is used to
 
337
    represent a session.
 
338
 
 
339
    @ivar _reactor: An object providing L{IReactorTime} to use for scheduling
 
340
        expiration.
 
341
    @ivar sessionTimeout: timeout of a session, in seconds.
 
342
    @ivar loopFactory: Deprecated in Twisted 9.0.  Does nothing.  Do not use.
 
343
    """
 
344
    sessionTimeout = 900
 
345
    loopFactory = task.LoopingCall
 
346
 
 
347
    _expireCall = None
 
348
 
 
349
    def __init__(self, site, uid, reactor=None):
 
350
        """
 
351
        Initialize a session with a unique ID for that session.
 
352
        """
 
353
        components.Componentized.__init__(self)
 
354
 
 
355
        if reactor is None:
 
356
            from twisted.internet import reactor
 
357
        self._reactor = reactor
 
358
 
 
359
        self.site = site
 
360
        self.uid = uid
 
361
        self.expireCallbacks = []
 
362
        self.touch()
 
363
        self.sessionNamespaces = {}
 
364
 
 
365
 
 
366
    def startCheckingExpiration(self, lifetime=None):
 
367
        """
 
368
        Start expiration tracking.
 
369
 
 
370
        @param lifetime: Ignored; deprecated.
 
371
 
 
372
        @return: C{None}
 
373
        """
 
374
        if lifetime is not None:
 
375
            warnings.warn(
 
376
                "The lifetime parameter to startCheckingExpiration is "
 
377
                "deprecated since Twisted 9.0.  See Session.sessionTimeout "
 
378
                "instead.", DeprecationWarning, stacklevel=2)
 
379
        self._expireCall = self._reactor.callLater(
 
380
            self.sessionTimeout, self.expire)
 
381
 
 
382
 
 
383
    def notifyOnExpire(self, callback):
 
384
        """
 
385
        Call this callback when the session expires or logs out.
 
386
        """
 
387
        self.expireCallbacks.append(callback)
 
388
 
 
389
 
 
390
    def expire(self):
 
391
        """
 
392
        Expire/logout of the session.
 
393
        """
 
394
        del self.site.sessions[self.uid]
 
395
        for c in self.expireCallbacks:
 
396
            c()
 
397
        self.expireCallbacks = []
 
398
        if self._expireCall and self._expireCall.active():
 
399
            self._expireCall.cancel()
 
400
            # Break reference cycle.
 
401
            self._expireCall = None
 
402
 
 
403
 
 
404
    def touch(self):
 
405
        """
 
406
        Notify session modification.
 
407
        """
 
408
        self.lastModified = self._reactor.seconds()
 
409
        if self._expireCall is not None:
 
410
            self._expireCall.reset(self.sessionTimeout)
 
411
 
 
412
 
 
413
    def checkExpired(self):
 
414
        """
 
415
        Deprecated; does nothing.
 
416
        """
 
417
        warnings.warn(
 
418
            "Session.checkExpired is deprecated since Twisted 9.0; sessions "
 
419
            "check themselves now, you don't need to.",
 
420
            stacklevel=2, category=DeprecationWarning)
 
421
 
 
422
 
 
423
version = "TwistedWeb/%s" % copyright.version
 
424
 
 
425
 
 
426
class Site(http.HTTPFactory):
 
427
    """
 
428
    A web site: manage log, sessions, and resources.
 
429
 
 
430
    @ivar counter: increment value used for generating unique sessions ID.
 
431
    @ivar requestFactory: factory creating requests objects. Default to
 
432
        L{Request}.
 
433
    @ivar displayTracebacks: if set, Twisted internal errors are displayed on
 
434
        rendered pages. Default to C{True}.
 
435
    @ivar sessionFactory: factory for sessions objects. Default to L{Session}.
 
436
    @ivar sessionCheckTime: Deprecated.  See L{Session.sessionTimeout} instead.
 
437
    """
 
438
    counter = 0
 
439
    requestFactory = Request
 
440
    displayTracebacks = True
 
441
    sessionFactory = Session
 
442
    sessionCheckTime = 1800
 
443
 
 
444
    def __init__(self, resource, logPath=None, timeout=60*60*12):
 
445
        """
 
446
        Initialize.
 
447
        """
 
448
        http.HTTPFactory.__init__(self, logPath=logPath, timeout=timeout)
 
449
        self.sessions = {}
 
450
        self.resource = resource
 
451
 
 
452
    def _openLogFile(self, path):
 
453
        from twisted.python import logfile
 
454
        return logfile.LogFile(os.path.basename(path), os.path.dirname(path))
 
455
 
 
456
    def __getstate__(self):
 
457
        d = self.__dict__.copy()
 
458
        d['sessions'] = {}
 
459
        return d
 
460
 
 
461
    def _mkuid(self):
 
462
        """
 
463
        (internal) Generate an opaque, unique ID for a user's session.
 
464
        """
 
465
        from twisted.python.hashlib import md5
 
466
        import random
 
467
        self.counter = self.counter + 1
 
468
        return md5("%s_%s" % (str(random.random()) , str(self.counter))).hexdigest()
 
469
 
 
470
    def makeSession(self):
 
471
        """
 
472
        Generate a new Session instance, and store it for future reference.
 
473
        """
 
474
        uid = self._mkuid()
 
475
        session = self.sessions[uid] = self.sessionFactory(self, uid)
 
476
        session.startCheckingExpiration()
 
477
        return session
 
478
 
 
479
    def getSession(self, uid):
 
480
        """
 
481
        Get a previously generated session, by its unique ID.
 
482
        This raises a KeyError if the session is not found.
 
483
        """
 
484
        return self.sessions[uid]
 
485
 
 
486
    def buildProtocol(self, addr):
 
487
        """
 
488
        Generate a channel attached to this site.
 
489
        """
 
490
        channel = http.HTTPFactory.buildProtocol(self, addr)
 
491
        channel.requestFactory = self.requestFactory
 
492
        channel.site = self
 
493
        return channel
 
494
 
 
495
    isLeaf = 0
 
496
 
 
497
    def render(self, request):
 
498
        """
 
499
        Redirect because a Site is always a directory.
 
500
        """
 
501
        request.redirect(request.prePathURL() + '/')
 
502
        request.finish()
 
503
 
 
504
    def getChildWithDefault(self, pathEl, request):
 
505
        """
 
506
        Emulate a resource's getChild method.
 
507
        """
 
508
        request.site = self
 
509
        return self.resource.getChildWithDefault(pathEl, request)
 
510
 
 
511
    def getResourceFor(self, request):
 
512
        """
 
513
        Get a resource for a request.
 
514
 
 
515
        This iterates through the resource heirarchy, calling
 
516
        getChildWithDefault on each resource it finds for a path element,
 
517
        stopping when it hits an element where isLeaf is true.
 
518
        """
 
519
        request.site = self
 
520
        # Sitepath is used to determine cookie names between distributed
 
521
        # servers and disconnected sites.
 
522
        request.sitepath = copy.copy(request.prepath)
 
523
        return resource.getChildForRequest(self.resource, request)
 
524
 
 
525
 
 
526
import html
 
527