123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- # -*- test-case-name: twisted.web.test.test_web -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Implementation of the lowest-level Resource class.
- """
- from __future__ import division, absolute_import
- __all__ = [
- 'IResource', 'getChildForRequest',
- 'Resource', 'ErrorPage', 'NoResource', 'ForbiddenResource',
- 'EncodingResourceWrapper']
- import warnings
- from zope.interface import Attribute, Interface, implementer
- from twisted.python.compat import nativeString, unicode
- from twisted.python.reflect import prefixedMethodNames
- from twisted.python.components import proxyForInterface
- from twisted.web._responses import FORBIDDEN, NOT_FOUND
- from twisted.web.error import UnsupportedMethod
- class IResource(Interface):
- """
- A web resource.
- """
- isLeaf = Attribute(
- """
- Signal if this IResource implementor is a "leaf node" or not. If True,
- getChildWithDefault will not be called on this Resource.
- """)
- def getChildWithDefault(name, request):
- """
- Return a child with the given name for the given request.
- This is the external interface used by the Resource publishing
- machinery. If implementing IResource without subclassing
- Resource, it must be provided. However, if subclassing Resource,
- getChild overridden instead.
- @param name: A single path component from a requested URL. For example,
- a request for I{http://example.com/foo/bar} will result in calls to
- this method with C{b"foo"} and C{b"bar"} as values for this
- argument.
- @type name: C{bytes}
- @param request: A representation of all of the information about the
- request that is being made for this child.
- @type request: L{twisted.web.server.Request}
- """
- def putChild(path, child):
- """
- Put a child IResource implementor at the given path.
- @param path: A single path component, to be interpreted relative to the
- path this resource is found at, at which to put the given child.
- For example, if resource A can be found at I{http://example.com/foo}
- then a call like C{A.putChild(b"bar", B)} will make resource B
- available at I{http://example.com/foo/bar}.
- @type path: C{bytes}
- """
- def render(request):
- """
- Render a request. This is called on the leaf resource for a request.
- @return: Either C{server.NOT_DONE_YET} to indicate an asynchronous or a
- C{bytes} instance to write as the response to the request. If
- C{NOT_DONE_YET} is returned, at some point later (for example, in a
- Deferred callback) call C{request.write(b"<html>")} to write data to
- the request, and C{request.finish()} to send the data to the
- browser.
- @raise twisted.web.error.UnsupportedMethod: If the HTTP verb
- requested is not supported by this resource.
- """
- def getChildForRequest(resource, request):
- """
- Traverse resource tree to find who will handle the request.
- """
- while request.postpath and not resource.isLeaf:
- pathElement = request.postpath.pop(0)
- request.prepath.append(pathElement)
- resource = resource.getChildWithDefault(pathElement, request)
- return resource
- @implementer(IResource)
- class Resource:
- """
- Define a web-accessible resource.
- This serves 2 main purposes; one is to provide a standard representation
- for what HTTP specification calls an 'entity', and the other is to provide
- an abstract directory structure for URL retrieval.
- """
- entityType = IResource
- server = None
- def __init__(self):
- """
- Initialize.
- """
- self.children = {}
- isLeaf = 0
- ### Abstract Collection Interface
- def listStaticNames(self):
- return list(self.children.keys())
- def listStaticEntities(self):
- return list(self.children.items())
- def listNames(self):
- return list(self.listStaticNames()) + self.listDynamicNames()
- def listEntities(self):
- return list(self.listStaticEntities()) + self.listDynamicEntities()
- def listDynamicNames(self):
- return []
- def listDynamicEntities(self, request=None):
- return []
- def getStaticEntity(self, name):
- return self.children.get(name)
- def getDynamicEntity(self, name, request):
- if name not in self.children:
- return self.getChild(name, request)
- else:
- return None
- def delEntity(self, name):
- del self.children[name]
- def reallyPutEntity(self, name, entity):
- self.children[name] = entity
- # Concrete HTTP interface
- def getChild(self, path, request):
- """
- Retrieve a 'child' resource from me.
- Implement this to create dynamic resource generation -- resources which
- are always available may be registered with self.putChild().
- This will not be called if the class-level variable 'isLeaf' is set in
- your subclass; instead, the 'postpath' attribute of the request will be
- left as a list of the remaining path elements.
- For example, the URL /foo/bar/baz will normally be::
- | site.resource.getChild('foo').getChild('bar').getChild('baz').
- However, if the resource returned by 'bar' has isLeaf set to true, then
- the getChild call will never be made on it.
- Parameters and return value have the same meaning and requirements as
- those defined by L{IResource.getChildWithDefault}.
- """
- return NoResource("No such child resource.")
- def getChildWithDefault(self, path, request):
- """
- Retrieve a static or dynamically generated child resource from me.
- First checks if a resource was added manually by putChild, and then
- call getChild to check for dynamic resources. Only override if you want
- to affect behaviour of all child lookups, rather than just dynamic
- ones.
- This will check to see if I have a pre-registered child resource of the
- given name, and call getChild if I do not.
- @see: L{IResource.getChildWithDefault}
- """
- if path in self.children:
- return self.children[path]
- return self.getChild(path, request)
- def getChildForRequest(self, request):
- warnings.warn("Please use module level getChildForRequest.", DeprecationWarning, 2)
- return getChildForRequest(self, request)
- def putChild(self, path, child):
- """
- Register a static child.
- You almost certainly don't want '/' in your path. If you
- intended to have the root of a folder, e.g. /foo/, you want
- path to be ''.
- @param path: A single path component.
- @type path: L{bytes}
- @param child: The child resource to register.
- @type child: L{IResource}
- @see: L{IResource.putChild}
- """
- if not isinstance(path, bytes):
- warnings.warn(
- 'Path segment must be bytes; '
- 'passing {0} has never worked, and '
- 'will raise an exception in the future.'
- .format(type(path)),
- category=DeprecationWarning,
- stacklevel=2)
- self.children[path] = child
- child.server = self.server
- def render(self, request):
- """
- Render a given resource. See L{IResource}'s render method.
- I delegate to methods of self with the form 'render_METHOD'
- where METHOD is the HTTP that was used to make the
- request. Examples: render_GET, render_HEAD, render_POST, and
- so on. Generally you should implement those methods instead of
- overriding this one.
- render_METHOD methods are expected to return a byte string which will be
- the rendered page, unless the return value is C{server.NOT_DONE_YET}, in
- which case it is this class's responsibility to write the results using
- C{request.write(data)} and then call C{request.finish()}.
- Old code that overrides render() directly is likewise expected
- to return a byte string or NOT_DONE_YET.
- @see: L{IResource.render}
- """
- m = getattr(self, 'render_' + nativeString(request.method), None)
- if not m:
- try:
- allowedMethods = self.allowedMethods
- except AttributeError:
- allowedMethods = _computeAllowedMethods(self)
- raise UnsupportedMethod(allowedMethods)
- return m(request)
- def render_HEAD(self, request):
- """
- Default handling of HEAD method.
- I just return self.render_GET(request). When method is HEAD,
- the framework will handle this correctly.
- """
- return self.render_GET(request)
- def _computeAllowedMethods(resource):
- """
- Compute the allowed methods on a C{Resource} based on defined render_FOO
- methods. Used when raising C{UnsupportedMethod} but C{Resource} does
- not define C{allowedMethods} attribute.
- """
- allowedMethods = []
- for name in prefixedMethodNames(resource.__class__, "render_"):
- # Potentially there should be an API for encode('ascii') in this
- # situation - an API for taking a Python native string (bytes on Python
- # 2, text on Python 3) and returning a socket-compatible string type.
- allowedMethods.append(name.encode('ascii'))
- return allowedMethods
- class ErrorPage(Resource):
- """
- L{ErrorPage} is a resource which responds with a particular
- (parameterized) status and a body consisting of HTML containing some
- descriptive text. This is useful for rendering simple error pages.
- @ivar template: A native string which will have a dictionary interpolated
- into it to generate the response body. The dictionary has the following
- keys:
- - C{"code"}: The status code passed to L{ErrorPage.__init__}.
- - C{"brief"}: The brief description passed to L{ErrorPage.__init__}.
- - C{"detail"}: The detailed description passed to
- L{ErrorPage.__init__}.
- @ivar code: An integer status code which will be used for the response.
- @type code: C{int}
- @ivar brief: A short string which will be included in the response body as
- the page title.
- @type brief: C{str}
- @ivar detail: A longer string which will be included in the response body.
- @type detail: C{str}
- """
- template = """
- <html>
- <head><title>%(code)s - %(brief)s</title></head>
- <body>
- <h1>%(brief)s</h1>
- <p>%(detail)s</p>
- </body>
- </html>
- """
- def __init__(self, status, brief, detail):
- Resource.__init__(self)
- self.code = status
- self.brief = brief
- self.detail = detail
- def render(self, request):
- request.setResponseCode(self.code)
- request.setHeader(b"content-type", b"text/html; charset=utf-8")
- interpolated = self.template % dict(
- code=self.code, brief=self.brief, detail=self.detail)
- if isinstance(interpolated, unicode):
- return interpolated.encode('utf-8')
- return interpolated
- def getChild(self, chnam, request):
- return self
- class NoResource(ErrorPage):
- """
- L{NoResource} is a specialization of L{ErrorPage} which returns the HTTP
- response code I{NOT FOUND}.
- """
- def __init__(self, message="Sorry. No luck finding that resource."):
- ErrorPage.__init__(self, NOT_FOUND, "No Such Resource", message)
- class ForbiddenResource(ErrorPage):
- """
- L{ForbiddenResource} is a specialization of L{ErrorPage} which returns the
- I{FORBIDDEN} HTTP response code.
- """
- def __init__(self, message="Sorry, resource is forbidden."):
- ErrorPage.__init__(self, FORBIDDEN, "Forbidden Resource", message)
- class _IEncodingResource(Interface):
- """
- A resource which knows about L{_IRequestEncoderFactory}.
- @since: 12.3
- """
- def getEncoder(request):
- """
- Parse the request and return an encoder if applicable, using
- L{_IRequestEncoderFactory.encoderForRequest}.
- @return: A L{_IRequestEncoder}, or L{None}.
- """
- @implementer(_IEncodingResource)
- class EncodingResourceWrapper(proxyForInterface(IResource)):
- """
- Wrap a L{IResource}, potentially applying an encoding to the response body
- generated.
- Note that the returned children resources won't be wrapped, so you have to
- explicitly wrap them if you want the encoding to be applied.
- @ivar encoders: A list of
- L{_IRequestEncoderFactory<twisted.web.iweb._IRequestEncoderFactory>}
- returning L{_IRequestEncoder<twisted.web.iweb._IRequestEncoder>} that
- may transform the data passed to C{Request.write}. The list must be
- sorted in order of priority: the first encoder factory handling the
- request will prevent the others from doing the same.
- @type encoders: C{list}.
- @since: 12.3
- """
- def __init__(self, original, encoders):
- super(EncodingResourceWrapper, self).__init__(original)
- self._encoders = encoders
- def getEncoder(self, request):
- """
- Browser the list of encoders looking for one applicable encoder.
- """
- for encoderFactory in self._encoders:
- encoder = encoderFactory.encoderForRequest(request)
- if encoder is not None:
- return encoder
|