root / trunk / twisted / web / resource.py

Revision 26116, 9.1 kB (checked in by exarkun, 5 months ago)

Merge deprecate-error-pages-3035

Author: exarkun
Reviewer: glyph, therve
Fixes: #3035

Move the error response resource classes from twisted.web.error to
twisted.web.resource. This is the first step in eliminating the
circular dependency between those two modules. Deprecated versions
of the resources remain in twisted.web.error for backwards compatibility
and numerous tests for these classes have been added.

Line 
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 Implementation of the lowest-level Resource class.
7 """
8
9 import warnings
10
11 from zope.interface import Attribute, implements, Interface
12
13 from twisted.web import http
14
15
16 class IResource(Interface):
17     """
18     A web resource.
19     """
20
21     isLeaf = Attribute(
22         """
23         Signal if this IResource implementor is a "leaf node" or not. If True,
24         getChildWithDefault will not be called on this Resource.
25         """)
26
27     def getChildWithDefault(name, request):
28         """
29         Return a child with the given name for the given request.
30         This is the external interface used by the Resource publishing
31         machinery. If implementing IResource without subclassing
32         Resource, it must be provided. However, if subclassing Resource,
33         getChild overridden instead.
34         """
35
36     def putChild(path, child):
37         """
38         Put a child IResource implementor at the given path.
39         """
40
41     def render(request):
42         """
43         Render a request. This is called on the leaf resource for
44         a request. Render must return either a string, which will
45         be sent to the browser as the HTML for the request, or
46         server.NOT_DONE_YET. If NOT_DONE_YET is returned,
47         at some point later (in a Deferred callback, usually)
48         call request.write("<html>") to write data to the request,
49         and request.finish() to send the data to the browser.
50         """
51
52
53
54 def getChildForRequest(resource, request):
55     """
56     Traverse resource tree to find who will handle the request.
57     """
58     while request.postpath and not resource.isLeaf:
59         pathElement = request.postpath.pop(0)
60         request.prepath.append(pathElement)
61         resource = resource.getChildWithDefault(pathElement, request)
62     return resource
63
64
65
66 class Resource:
67     """
68     I define a web-accessible resource.
69
70     I serve 2 main purposes; one is to provide a standard representation for
71     what HTTP specification calls an 'entity', and the other is to provide an
72     abstract directory structure for URL retrieval.
73     """
74
75     implements(IResource)
76
77     entityType = IResource
78
79     server = None
80
81     def __init__(self):
82         """Initialize.
83         """
84         self.children = {}
85
86     isLeaf = 0
87
88     ### Abstract Collection Interface
89
90     def listStaticNames(self):
91         return self.children.keys()
92
93     def listStaticEntities(self):
94         return self.children.items()
95
96     def listNames(self):
97         return self.listStaticNames() + self.listDynamicNames()
98
99     def listEntities(self):
100         return self.listStaticEntities() + self.listDynamicEntities()
101
102     def listDynamicNames(self):
103         return []
104
105     def listDynamicEntities(self, request=None):
106         return []
107
108     def getStaticEntity(self, name):
109         return self.children.get(name)
110
111     def getDynamicEntity(self, name, request):
112         if not self.children.has_key(name):
113             return self.getChild(name, request)
114         else:
115             return None
116
117     def delEntity(self, name):
118         del self.children[name]
119
120     def reallyPutEntity(self, name, entity):
121         self.children[name] = entity
122
123     # Concrete HTTP interface
124
125     def getChild(self, path, request):
126         """
127         Retrieve a 'child' resource from me.
128
129         Implement this to create dynamic resource generation -- resources which
130         are always available may be registered with self.putChild().
131
132         This will not be called if the class-level variable 'isLeaf' is set in
133         your subclass; instead, the 'postpath' attribute of the request will be
134         left as a list of the remaining path elements.
135
136         For example, the URL /foo/bar/baz will normally be::
137
138           | site.resource.getChild('foo').getChild('bar').getChild('baz').
139
140         However, if the resource returned by 'bar' has isLeaf set to true, then
141         the getChild call will never be made on it.
142
143         @param path: a string, describing the child
144
145         @param request: a twisted.web.server.Request specifying meta-information
146                         about the request that is being made for this child.
147         """
148         return NoResource("No such child resource.")
149
150
151     def getChildWithDefault(self, path, request):
152         """
153         Retrieve a static or dynamically generated child resource from me.
154
155         First checks if a resource was added manually by putChild, and then
156         call getChild to check for dynamic resources. Only override if you want
157         to affect behaviour of all child lookups, rather than just dynamic
158         ones.
159
160         This will check to see if I have a pre-registered child resource of the
161         given name, and call getChild if I do not.
162         """
163         if path in self.children:
164             return self.children[path]
165         return self.getChild(path, request)
166
167
168     def getChildForRequest(self, request):
169         warnings.warn("Please use module level getChildForRequest.", DeprecationWarning, 2)
170         return getChildForRequest(self, request)
171
172
173     def putChild(self, path, child):
174         """
175         Register a static child.
176
177         You almost certainly don't want '/' in your path. If you
178         intended to have the root of a folder, e.g. /foo/, you want
179         path to be ''.
180         """
181         self.children[path] = child
182         child.server = self.server
183
184
185     def render(self, request):
186         """
187         Render a given resource. See L{IResource}'s render method.
188
189         I delegate to methods of self with the form 'render_METHOD'
190         where METHOD is the HTTP that was used to make the
191         request. Examples: render_GET, render_HEAD, render_POST, and
192         so on. Generally you should implement those methods instead of
193         overriding this one.
194
195         render_METHOD methods are expected to return a string which
196         will be the rendered page, unless the return value is
197         twisted.web.server.NOT_DONE_YET, in which case it is this
198         class's responsibility to write the results to
199         request.write(data), then call request.finish().
200
201         Old code that overrides render() directly is likewise expected
202         to return a string or NOT_DONE_YET.
203         """
204         m = getattr(self, 'render_' + request.method, None)
205         if not m:
206             # This needs to be here until the deprecated subclasses of the
207             # below three error resources in twisted.web.error are removed.
208             from twisted.web.error import UnsupportedMethod
209             raise UnsupportedMethod(getattr(self, 'allowedMethods', ()))
210         return m(request)
211
212
213     def render_HEAD(self, request):
214         """
215         Default handling of HEAD method.
216
217         I just return self.render_GET(request). When method is HEAD,
218         the framework will handle this correctly.
219         """
220         return self.render_GET(request)
221
222
223
224 class ErrorPage(Resource):
225     """
226     L{ErrorPage} is a resource which responds with a particular
227     (parameterized) status and a body consisting of HTML containing some
228     descriptive text.  This is useful for rendering simple error pages.
229
230     @ivar template: A C{str} which will have a dictionary interpolated into
231         it to generate the response body.  The dictionary has the following
232         keys:
233
234           - C{"code"}: The status code passed to L{ErrorPage.__init__}.
235           - C{"brief"}: The brief description passed to L{ErrorPage.__init__}.
236           - C{"detail"}: The detailed description passed to
237             L{ErrorPage.__init__}.
238
239     @ivar code: An integer status code which will be used for the response.
240     @ivar brief: A short string which will be included in the response body.
241     @ivar detail: A longer string which will be included in the response body.
242     """
243
244     template = """
245 <html>
246   <head><title>%(code)s - %(brief)s</title></head>
247   <body>
248     <h1>%(brief)s</h1>
249     <p>%(detail)s</p>
250   </body>
251 </html>
252 """
253
254     def __init__(self, status, brief, detail):
255         Resource.__init__(self)
256         self.code = status
257         self.brief = brief
258         self.detail = detail
259
260
261     def render(self, request):
262         request.setResponseCode(self.code)
263         request.setHeader("content-type", "text/html")
264         return self.template % dict(
265             code=self.code,
266             brief=self.brief,
267             detail=self.detail)
268
269
270     def getChild(self, chnam, request):
271         return self
272
273
274
275 class NoResource(ErrorPage):
276     """
277     L{NoResource} is a specialization of L{ErrorPage} which returns the HTTP
278     response code I{NOT FOUND}.
279     """
280     def __init__(self, message="Sorry. No luck finding that resource."):
281         ErrorPage.__init__(self, http.NOT_FOUND,
282                            "No Such Resource",
283                            message)
284
285
286
287 class ForbiddenResource(ErrorPage):
288     """
289     L{ForbiddenResource} is a specialization of L{ErrorPage} which returns the
290     I{FORBIDDEN} HTTP response code.
291     """
292     def __init__(self, message="Sorry, resource is forbidden."):
293         ErrorPage.__init__(self, http.FORBIDDEN,
294                            "Forbidden Resource",
295                            message)
296
297
298 __all__ = [
299     'IResource', 'getChildForRequest',
300     'Resource', 'ErrorPage', 'NoResource', 'ForbiddenResource']
Note: See TracBrowser for help on using the browser.