# -*- test-case-name: twisted.web.test.test_web -*-
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
# See LICENSE for details.
"""This is a web-server which integrates with the twisted.internet
infrastructure.
"""
# System Imports
try:
import cStringIO as StringIO
except ImportError:
import StringIO
import base64
import string
import socket
import types
import operator
import cgi
import copy
import time
import os
from urllib import quote
try:
from twisted.protocols._c_urlarg import unquote
except ImportError:
from urllib import unquote
#some useful constants
NOT_DONE_YET = 1
# Twisted Imports
from twisted.spread import pb
from twisted.internet import reactor, protocol, defer, address, task
from twisted.web import http
from twisted.python import log, reflect, roots, failure, components
from twisted import copyright
from twisted.cred import util
from twisted.persisted import styles
# Sibling Imports
import error, resource
from twisted.web import util as webutil
# backwards compatability
date_time_string = http.datetimeToString
string_date_time = http.stringToDatetime
# Support for other methods may be implemented on a per-resource basis.
supportedMethods = ('GET', 'HEAD', 'POST')
class UnsupportedMethod(Exception):
"""Raised by a resource when faced with a strange request method.
RFC 2616 (HTTP 1.1) gives us two choices when faced with this situtation:
If the type of request is known to us, but not allowed for the requested
resource, respond with NOT_ALLOWED. Otherwise, if the request is something
we don't know how to deal with in any case, respond with NOT_IMPLEMENTED.
When this exception is raised by a Resource's render method, the server
will make the appropriate response.
This exception's first argument MUST be a sequence of the methods the
resource *does* support.
"""
allowedMethods = ()
def __init__(self, allowedMethods, *args):
Exception.__init__(self, allowedMethods, *args)
self.allowedMethods = allowedMethods
if not operator.isSequenceType(allowedMethods):
why = "but my first argument is not a sequence."
s = ("First argument must be a sequence of"
" supported methods, %s" % (why,))
raise TypeError, s
def _addressToTuple(addr):
if isinstance(addr, address.IPv4Address):
return ('INET', addr.host, addr.port)
elif isinstance(addr, address.UNIXAddress):
return ('UNIX', addr.name)
else:
return tuple(addr)
class Request(pb.Copyable, http.Request, components.Componentized):
site = None
appRootURL = None
__pychecker__ = 'unusednames=issuer'
def __init__(self, *args, **kw):
http.Request.__init__(self, *args, **kw)
components.Componentized.__init__(self)
self.notifications = []
def getStateToCopyFor(self, issuer):
x = self.__dict__.copy()
del x['transport']
# XXX refactor this attribute out; it's from protocol
# del x['server']
del x['channel']
del x['content']
del x['site']
self.content.seek(0, 0)
x['content_data'] = self.content.read()
x['remote'] = pb.ViewPoint(issuer, self)
# Address objects aren't jellyable
x['host'] = _addressToTuple(x['host'])
x['client'] = _addressToTuple(x['client'])
return x
# HTML generation helpers
def sibLink(self, name):
"Return the text that links to a sibling of the requested resource."
if self.postpath:
return (len(self.postpath)*"../") + name
else:
return name
def childLink(self, name):
"Return the text that links to a child of the requested resource."
lpp = len(self.postpath)
if lpp > 1:
return ((lpp-1)*"../") + name
elif lpp == 1:
return name
else: # lpp == 0
if len(self.prepath) and self.prepath[-1]:
return self.prepath[-1] + '/' + name
else:
return name
def process(self):
"Process a request."
# get site from channel
self.site = self.channel.site
# set various default headers
self.setHeader('server', version)
self.setHeader('date', http.datetimeToString())
self.setHeader('content-type', "text/html")
# Resource Identification
self.prepath = []
self.postpath = map(unquote, string.split(self.path[1:], '/'))
try:
resrc = self.site.getResourceFor(self)
self.render(resrc)
except:
self.processingFailed(failure.Failure())
def render(self, resrc):
try:
body = resrc.render(self)
except UnsupportedMethod, e:
allowedMethods = e.allowedMethods
if (self.method == "HEAD") and ("GET" in allowedMethods):
# We must support HEAD (RFC 2616, 5.1.1). If the
# resource doesn't, fake it by giving the resource
# a 'GET' request and then return only the headers,
# not the body.
log.msg("Using GET to fake a HEAD request for %s" %
(resrc,))
self.method = "GET"
body = resrc.render(self)
if body is NOT_DONE_YET:
log.msg("Tried to fake a HEAD request for %s, but "
"it got away from me." % resrc)
# Oh well, I guess we won't include the content length.
else:
self.setHeader('content-length', str(len(body)))
self.write('')
self.finish()
return
if self.method in (supportedMethods):
# We MUST include an Allow header
# (RFC 2616, 10.4.6 and 14.7)
self.setHeader('Allow', allowedMethods)
s = ('''Your browser approached me (at %(URI)s) with'''
''' the method "%(method)s". I only allow'''
''' the method%(plural)s %(allowed)s here.''' % {
'URI': self.uri,
'method': self.method,
'plural': ((len(allowedMethods) > 1) and 's') or '',
'allowed': string.join(allowedMethods, ', ')
})
epage = error.ErrorPage(http.NOT_ALLOWED,
"Method Not Allowed", s)
body = epage.render(self)
else:
epage = error.ErrorPage(http.NOT_IMPLEMENTED, "Huh?",
"""I don't know how to treat a"""
""" %s request."""
% (self.method))
body = epage.render(self)
# end except UnsupportedMethod
if body == NOT_DONE_YET:
return
if type(body) is not types.StringType:
body = error.ErrorPage(http.INTERNAL_SERVER_ERROR,
"Request did not return a string",
"Request: "+html.PRE(reflect.safe_repr(self))+"
"+
"Resource: "+html.PRE(reflect.safe_repr(resrc))+"
"+
"Value: "+html.PRE(reflect.safe_repr(body))).render(self)
if self.method == "HEAD":
if len(body) > 0:
# This is a Bad Thing (RFC 2616, 9.4)
log.msg("Warning: HEAD request %s for resource %s is"
" returning a message body."
" I think I'll eat it."
% (self, resrc))
self.setHeader('content-length', str(len(body)))
self.write('')
else:
self.setHeader('content-length', str(len(body)))
self.write(body)
self.finish()
def processingFailed(self, reason):
log.err(reason)
if self.site.displayTracebacks:
body = ("