root / trunk / twisted / web2 / resource.py

Revision 22911, 10.0 kB (checked in by wsanchez, 1 year ago)

Merge r22859 and r22910 from branches/dav-take-two-3081-1: Invalid request object from renderHTTP()

renderHTTP() calls http_* with bad request if checkPreconditions() defers.

Author: wsanchez
Reviewer: foom
Fixes #3084

Line 
1 # -*- test-case-name: twisted.web2.test.test_server,twisted.web2.test.test_resource -*-
2 # Copyright (c) 2001-2007 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 I hold the lowest-level L{Resource} class and related mix-in classes.
7 """
8
9 # System Imports
10 from zope.interface import implements
11
12 from twisted.web2 import iweb, http, server, responsecode
13
14 class RenderMixin(object):
15     """
16     Mix-in class for L{iweb.IResource} which provides a dispatch mechanism for
17     handling HTTP methods.
18     """
19     def allowedMethods(self):
20         """
21         @return: A tuple of HTTP methods that are allowed to be invoked on this resource.
22         """
23         if not hasattr(self, "_allowed_methods"):
24             self._allowed_methods = tuple([name[5:] for name in dir(self) if name.startswith('http_')])
25         return self._allowed_methods
26
27     def checkPreconditions(self, request):
28         """
29         Checks all preconditions imposed by this resource upon a request made
30         against it.
31         @param request: the request to process.
32         @raise http.HTTPError: if any precondition fails.
33         @return: C{None} or a deferred whose callback value is C{request}.
34         """
35         #
36         # http.checkPreconditions() gets called by the server after every
37         # GET or HEAD request.
38         #
39         # For other methods, we need to know to bail out before request
40         # processing, especially for methods that modify server state (eg. PUT).
41         # We also would like to do so even for methods that don't, if those
42         # methods might be expensive to process.  We're assuming that GET and
43         # HEAD are not expensive.
44         #
45         if request.method not in ("GET", "HEAD"):
46             http.checkPreconditions(request)
47
48         # Check per-method preconditions
49         method = getattr(self, "preconditions_" + request.method, None)
50         if method:
51             return method(request)
52
53     def renderHTTP(self, request):
54         """
55         See L{iweb.IResource.renderHTTP}.
56
57         This implementation will dispatch the given C{request} to another method
58         of C{self} named C{http_}METHOD, where METHOD is the HTTP method used by
59         C{request} (eg. C{http_GET}, C{http_POST}, etc.).
60
61         Generally, a subclass should implement those methods instead of
62         overriding this one.
63
64         C{http_*} methods are expected provide the same interface and return the
65         same results as L{iweb.IResource}C{.renderHTTP} (and therefore this method).
66
67         C{etag} and C{last-modified} are added to the response returned by the
68         C{http_*} header, if known.
69
70         If an appropriate C{http_*} method is not found, a
71         L{responsecode.NOT_ALLOWED}-status response is returned, with an
72         appropriate C{allow} header.
73
74         @param request: the request to process.
75         @return: an object adaptable to L{iweb.IResponse}.
76         """
77         method = getattr(self, "http_" + request.method, None)
78         if not method:
79             response = http.Response(responsecode.NOT_ALLOWED)
80             response.headers.setHeader("allow", self.allowedMethods())
81             return response
82
83         d = self.checkPreconditions(request)
84         if d is None:
85             return method(request)
86         else:
87             return d.addCallback(lambda _: method(request))
88
89     def http_OPTIONS(self, request):
90         """
91         Respond to a OPTIONS request.
92         @param request: the request to process.
93         @return: an object adaptable to L{iweb.IResponse}.
94         """
95         response = http.Response(responsecode.OK)
96         response.headers.setHeader("allow", self.allowedMethods())
97         return response
98
99     def http_TRACE(self, request):
100         """
101         Respond to a TRACE request.
102         @param request: the request to process.
103         @return: an object adaptable to L{iweb.IResponse}.
104         """
105         return server.doTrace(request)
106
107     def http_HEAD(self, request):
108         """
109         Respond to a HEAD request.
110         @param request: the request to process.
111         @return: an object adaptable to L{iweb.IResponse}.
112         """
113         return self.http_GET(request)
114
115     def http_GET(self, request):
116         """
117         Respond to a GET request.
118
119         This implementation validates that the request body is empty and then
120         dispatches the given C{request} to L{render} and returns its result.
121
122         @param request: the request to process.
123         @return: an object adaptable to L{iweb.IResponse}.
124         """
125         if request.stream.length != 0:
126             return responsecode.REQUEST_ENTITY_TOO_LARGE
127
128         return self.render(request)
129
130     def render(self, request):
131         """
132         Subclasses should implement this method to do page rendering.
133         See L{http_GET}.
134         @param request: the request to process.
135         @return: an object adaptable to L{iweb.IResponse}.
136         """
137         raise NotImplementedError("Subclass must implement render method.")
138
139 class Resource(RenderMixin):
140     """
141     An L{iweb.IResource} implementation with some convenient mechanisms for
142     locating children.
143     """
144     implements(iweb.IResource)
145
146     addSlash = False
147
148     def locateChild(self, request, segments):
149         """
150         Locates a child resource of this resource.
151         @param request: the request to process.
152         @param segments: a sequence of URL path segments.
153         @return: a tuple of C{(child, segments)} containing the child
154         of this resource which matches one or more of the given C{segments} in
155         sequence, and a list of remaining segments.
156         """
157         w = getattr(self, 'child_%s' % (segments[0], ), None)
158
159         if w:
160             r = iweb.IResource(w, None)
161             if r:
162                 return r, segments[1:]
163             return w(request), segments[1:]
164
165         factory = getattr(self, 'childFactory', None)
166         if factory is not None:
167             r = factory(request, segments[0])
168             if r:
169                 return r, segments[1:]
170
171         return None, []
172
173     def child_(self, request):
174         """
175         This method locates a child with a trailing C{"/"} in the URL.
176         @param request: the request to process.
177         """
178         if self.addSlash and len(request.postpath) == 1:
179             return self
180         return None
181
182     def putChild(self, path, child):
183         """
184         Register a static child.
185
186         This implementation registers children by assigning them to attributes
187         with a C{child_} prefix.  C{resource.putChild("foo", child)} is
188         therefore same as C{o.child_foo = child}.
189
190         @param path: the name of the child to register.  You almost certainly
191             don't want C{"/"} in C{path}. If you want to add a "directory"
192             resource (e.g. C{/foo/}) specify C{path} as C{""}.
193         @param child: an object adaptable to L{iweb.IResource}.
194         """
195         setattr(self, 'child_%s' % (path, ), child)
196
197     def http_GET(self, request):
198         if self.addSlash and request.prepath[-1] != '':
199             # If this is a directory-ish resource...
200             return http.RedirectResponse(request.unparseURL(path=request.path+'/'))
201
202         return super(Resource, self).http_GET(request)
203
204
205 class PostableResource(Resource):
206     """
207     A L{Resource} capable of handling the POST request method.
208
209     @cvar maxMem: maximum memory used during the parsing of the data.
210     @type maxMem: C{int}
211     @cvar maxFields: maximum number of form fields allowed.
212     @type maxFields: C{int}
213     @cvar maxSize: maximum size of the whole post allowed.
214     @type maxSize: C{int}
215     """
216     maxMem = 100 * 1024
217     maxFields = 1024
218     maxSize = 10 * 1024 * 1024
219
220     def http_POST(self, request):
221         """
222         Respond to a POST request.
223         Reads and parses the incoming body data then calls L{render}.
224
225         @param request: the request to process.
226         @return: an object adaptable to L{iweb.IResponse}.
227         """
228         return server.parsePOSTData(request,
229             self.maxMem, self.maxFields, self.maxSize
230             ).addCallback(lambda res: self.render(request))
231
232
233 class LeafResource(RenderMixin):
234     """
235     A L{Resource} with no children.
236     """
237     implements(iweb.IResource)
238
239     def locateChild(self, request, segments):
240         return self, server.StopTraversal
241
242 class RedirectResource(LeafResource):
243     """
244     A L{LeafResource} which always performs a redirect.
245     """
246     implements(iweb.IResource)
247
248     def __init__(self, *args, **kwargs):
249         """
250         Parameters are URL components and are the same as those for
251         L{urlparse.urlunparse}.  URL components which are not specified will
252         default to the corresponding component of the URL of the request being
253         redirected.
254         """
255         self._args   = args
256         self._kwargs = kwargs
257
258     def renderHTTP(self, request):
259         return http.RedirectResponse(request.unparseURL(*self._args, **self._kwargs))
260
261 class WrapperResource(object):
262     """
263     An L{iweb.IResource} implementation which wraps a L{RenderMixin} instance
264     and provides a hook in which a subclass can implement logic that is called
265     before request processing on the contained L{Resource}.
266     """
267     implements(iweb.IResource)
268
269     def __init__(self, resource):
270         self.resource=resource
271
272     def hook(self, request):
273         """
274         Override this method in order to do something before passing control on
275         to the wrapped resource's C{renderHTTP} and C{locateChild} methods.
276         @return: None or a L{Deferred}.  If a deferred object is
277             returned, it's value is ignored, but C{renderHTTP} and
278             C{locateChild} are chained onto the deferred as callbacks.
279         """
280         raise NotImplementedError()
281
282     def locateChild(self, request, segments):
283         x = self.hook(request)
284         if x is not None:
285             return x.addCallback(lambda data: (self.resource, segments))
286         return self.resource, segments
287
288     def renderHTTP(self, request):
289         x = self.hook(request)
290         if x is not None:
291             return x.addCallback(lambda data: self.resource)
292         return self.resource
293
294
295 __all__ = ['RenderMixin', 'Resource', 'PostableResource', 'LeafResource', 'WrapperResource']
Note: See TracBrowser for help on using the browser.