1
# -*- test-case-name: twisted.web.test.test_web -*-
2
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
3
# See LICENSE for details.
7
This is a web-server which integrates with the twisted.internet
18
from urllib import quote
20
from zope.interface import implements
23
from twisted.protocols._c_urlarg import unquote
25
from urllib import unquote
27
#some useful constants
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
39
# backwards compatability
40
date_time_string = http.datetimeToString
41
string_date_time = http.stringToDatetime
43
# Support for other methods may be implemented on a per-resource basis.
44
supportedMethods = ('GET', 'HEAD', 'POST')
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)
55
class Request(pb.Copyable, http.Request, components.Componentized):
56
implements(iweb.IRequest)
60
__pychecker__ = 'unusednames=issuer'
62
def __init__(self, *args, **kw):
63
http.Request.__init__(self, *args, **kw)
64
components.Componentized.__init__(self)
66
def getStateToCopyFor(self, issuer):
67
x = self.__dict__.copy()
69
# XXX refactor this attribute out; it's from protocol
74
self.content.seek(0, 0)
75
x['content_data'] = self.content.read()
76
x['remote'] = pb.ViewPoint(issuer, self)
78
# Address objects aren't jellyable
79
x['host'] = _addressToTuple(x['host'])
80
x['client'] = _addressToTuple(x['client'])
82
# Header objects also aren't jellyable.
83
x['requestHeaders'] = list(x['requestHeaders'].getAllRawHeaders())
87
# HTML generation helpers
89
def sibLink(self, name):
90
"Return the text that links to a sibling of the requested resource."
92
return (len(self.postpath)*"../") + name
96
def childLink(self, name):
97
"Return the text that links to a child of the requested resource."
98
lpp = len(self.postpath)
100
return ((lpp-1)*"../") + name
104
if len(self.prepath) and self.prepath[-1]:
105
return self.prepath[-1] + '/' + name
112
# get site from channel
113
self.site = self.channel.site
115
# set various default headers
116
self.setHeader('server', version)
117
self.setHeader('date', http.datetimeToString())
118
self.setHeader('content-type', "text/html")
120
# Resource Identification
122
self.postpath = map(unquote, string.split(self.path[1:], '/'))
124
resrc = self.site.getResourceFor(self)
127
self.processingFailed(failure.Failure())
130
def render(self, resrc):
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,
140
log.msg("Using GET to fake a HEAD request for %s" %
143
body = resrc.render(self)
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.
150
self.setHeader('content-length', str(len(body)))
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.''' % {
164
'method': self.method,
165
'plural': ((len(allowedMethods) > 1) and 's') or '',
166
'allowed': string.join(allowedMethods, ', ')
168
epage = resource.ErrorPage(http.NOT_ALLOWED,
169
"Method Not Allowed", s)
170
body = epage.render(self)
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
178
if body == NOT_DONE_YET:
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)
188
if self.method == "HEAD":
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."
195
self.setHeader('content-length', str(len(body)))
198
self.setHeader('content-length', str(len(body)))
202
def processingFailed(self, 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))
210
body = ("<html><head><title>Processing Failed</title></head><body>"
211
"<b>Processing Failed</b></body></html>")
213
self.setResponseCode(http.INTERNAL_SERVER_ERROR)
214
self.setHeader('content-type',"text/html")
215
self.setHeader('content-length', str(len(body)))
220
def view_write(self, issuer, data):
221
"""Remote version of write; same interface.
225
def view_finish(self, issuer):
226
"""Remote version of finish; same interface.
230
def view_addCookie(self, issuer, k, v, **kwargs):
231
"""Remote version of addCookie; same interface.
233
self.addCookie(k, v, **kwargs)
235
def view_setHeader(self, issuer, k, v):
236
"""Remote version of setHeader; same interface.
240
def view_setLastModified(self, issuer, when):
241
"""Remote version of setLastModified; same interface.
243
self.setLastModified(when)
245
def view_setETag(self, issuer, tag):
246
"""Remote version of setETag; same interface.
250
def view_setResponseCode(self, issuer, code):
251
"""Remote version of setResponseCode; same interface.
253
self.setResponseCode(code)
255
def view_registerProducer(self, issuer, producer, streaming):
256
"""Remote version of registerProducer; same interface.
257
(requires a remote producer.)
259
self.registerProducer(_RemoteProducerWrapper(producer), streaming)
261
def view_unregisterProducer(self, issuer):
262
self.unregisterProducer()
264
### these calls remain local
268
def getSession(self, sessionInterface = None):
271
cookiename = string.join(['TWISTED_SESSION'] + self.sitepath, "_")
272
sessionCookie = self.getCookie(cookiename)
275
self.session = self.site.getSession(sessionCookie)
278
# if it still hasn't been set, fix it up.
280
self.session = self.site.makeSession()
281
self.addCookie(cookiename, self.session.uid, path='/')
284
return self.session.getComponent(sessionInterface)
287
def _prePathURL(self, prepath):
288
port = self.getHost().port
296
hostport = ':%d' % port
297
return 'http%s://%s%s/%s' % (
298
self.isSecure() and 's' or '',
299
self.getRequestHostname(),
301
'/'.join([quote(segment, safe='') for segment in prepath]))
303
def prePathURL(self):
304
return self._prePathURL(self.prepath)
307
from twisted.python import urlpath
308
return urlpath.URLPath.fromRequest(self)
310
def rememberRootURL(self):
312
Remember the currently-processed part of the URL for later
315
url = self._prePathURL(self.prepath[:-1])
316
self.appRootURL = url
318
def getRootURL(self):
320
Get a previously-remembered URL.
322
return self.appRootURL
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")
332
class Session(components.Componentized):
334
A user's session with a system.
336
This utility class contains no functionality, but is used to
339
@ivar _reactor: An object providing L{IReactorTime} to use for scheduling
341
@ivar sessionTimeout: timeout of a session, in seconds.
342
@ivar loopFactory: Deprecated in Twisted 9.0. Does nothing. Do not use.
345
loopFactory = task.LoopingCall
349
def __init__(self, site, uid, reactor=None):
351
Initialize a session with a unique ID for that session.
353
components.Componentized.__init__(self)
356
from twisted.internet import reactor
357
self._reactor = reactor
361
self.expireCallbacks = []
363
self.sessionNamespaces = {}
366
def startCheckingExpiration(self, lifetime=None):
368
Start expiration tracking.
370
@param lifetime: Ignored; deprecated.
374
if lifetime is not None:
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)
383
def notifyOnExpire(self, callback):
385
Call this callback when the session expires or logs out.
387
self.expireCallbacks.append(callback)
392
Expire/logout of the session.
394
del self.site.sessions[self.uid]
395
for c in self.expireCallbacks:
397
self.expireCallbacks = []
398
if self._expireCall and self._expireCall.active():
399
self._expireCall.cancel()
400
# Break reference cycle.
401
self._expireCall = None
406
Notify session modification.
408
self.lastModified = self._reactor.seconds()
409
if self._expireCall is not None:
410
self._expireCall.reset(self.sessionTimeout)
413
def checkExpired(self):
415
Deprecated; does nothing.
418
"Session.checkExpired is deprecated since Twisted 9.0; sessions "
419
"check themselves now, you don't need to.",
420
stacklevel=2, category=DeprecationWarning)
423
version = "TwistedWeb/%s" % copyright.version
426
class Site(http.HTTPFactory):
428
A web site: manage log, sessions, and resources.
430
@ivar counter: increment value used for generating unique sessions ID.
431
@ivar requestFactory: factory creating requests objects. Default to
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.
439
requestFactory = Request
440
displayTracebacks = True
441
sessionFactory = Session
442
sessionCheckTime = 1800
444
def __init__(self, resource, logPath=None, timeout=60*60*12):
448
http.HTTPFactory.__init__(self, logPath=logPath, timeout=timeout)
450
self.resource = resource
452
def _openLogFile(self, path):
453
from twisted.python import logfile
454
return logfile.LogFile(os.path.basename(path), os.path.dirname(path))
456
def __getstate__(self):
457
d = self.__dict__.copy()
463
(internal) Generate an opaque, unique ID for a user's session.
465
from twisted.python.hashlib import md5
467
self.counter = self.counter + 1
468
return md5("%s_%s" % (str(random.random()) , str(self.counter))).hexdigest()
470
def makeSession(self):
472
Generate a new Session instance, and store it for future reference.
475
session = self.sessions[uid] = self.sessionFactory(self, uid)
476
session.startCheckingExpiration()
479
def getSession(self, uid):
481
Get a previously generated session, by its unique ID.
482
This raises a KeyError if the session is not found.
484
return self.sessions[uid]
486
def buildProtocol(self, addr):
488
Generate a channel attached to this site.
490
channel = http.HTTPFactory.buildProtocol(self, addr)
491
channel.requestFactory = self.requestFactory
497
def render(self, request):
499
Redirect because a Site is always a directory.
501
request.redirect(request.prePathURL() + '/')
504
def getChildWithDefault(self, pathEl, request):
506
Emulate a resource's getChild method.
509
return self.resource.getChildWithDefault(pathEl, request)
511
def getResourceFor(self, request):
513
Get a resource for a request.
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.
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)