| Home | Trees | Indices | Help |
|
|---|
|
|
1 """Test the various means of instantiating and invoking tools."""
2
3 import gzip
4 import sys
5 from cherrypy._cpcompat import BytesIO, copyitems, itervalues
6 from cherrypy._cpcompat import IncompleteRead, ntob, ntou, py3k, xrange
7 import time
8 timeout = 0.2
9 import types
10
11 import cherrypy
12 from cherrypy import tools
13
14
15 europoundUnicode = ntou('\x80\xa3')
16
17
18 # Client-side code #
19
20 from cherrypy.test import helper
21
22
25
26 # Put check_access in a custom toolbox with its own namespace
27 myauthtools = cherrypy._cptools.Toolbox("myauth")
28
29 def check_access(default=False):
30 if not getattr(cherrypy.request, "userid", default):
31 raise cherrypy.HTTPError(401)
32 myauthtools.check_access = cherrypy.Tool('before_request_body', check_access)
33
34 def numerify():
35 def number_it(body):
36 for chunk in body:
37 for k, v in cherrypy.request.numerify_map:
38 chunk = chunk.replace(k, v)
39 yield chunk
40 cherrypy.response.body = number_it(cherrypy.response.body)
41
42 class NumTool(cherrypy.Tool):
43 def _setup(self):
44 def makemap():
45 m = self._merged_args().get("map", {})
46 cherrypy.request.numerify_map = copyitems(m)
47 cherrypy.request.hooks.attach('on_start_resource', makemap)
48
49 def critical():
50 cherrypy.request.error_response = cherrypy.HTTPError(502).set_response
51 critical.failsafe = True
52
53 cherrypy.request.hooks.attach('on_start_resource', critical)
54 cherrypy.request.hooks.attach(self._point, self.callable)
55
56 tools.numerify = NumTool('before_finalize', numerify)
57
58 # It's not mandatory to inherit from cherrypy.Tool.
59 class NadsatTool:
60
61 def __init__(self):
62 self.ended = {}
63 self._name = "nadsat"
64
65 def nadsat(self):
66 def nadsat_it_up(body):
67 for chunk in body:
68 chunk = chunk.replace(ntob("good"), ntob("horrorshow"))
69 chunk = chunk.replace(ntob("piece"), ntob("lomtick"))
70 yield chunk
71 cherrypy.response.body = nadsat_it_up(cherrypy.response.body)
72 nadsat.priority = 0
73
74 def cleanup(self):
75 # This runs after the request has been completely written out.
76 cherrypy.response.body = [ntob("razdrez")]
77 id = cherrypy.request.params.get("id")
78 if id:
79 self.ended[id] = True
80 cleanup.failsafe = True
81
82 def _setup(self):
83 cherrypy.request.hooks.attach('before_finalize', self.nadsat)
84 cherrypy.request.hooks.attach('on_end_request', self.cleanup)
85 tools.nadsat = NadsatTool()
86
87 def pipe_body():
88 cherrypy.request.process_request_body = False
89 clen = int(cherrypy.request.headers['Content-Length'])
90 cherrypy.request.body = cherrypy.request.rfile.read(clen)
91
92 # Assert that we can use a callable object instead of a function.
93 class Rotator(object):
94 def __call__(self, scale):
95 r = cherrypy.response
96 r.collapse_body()
97 if py3k:
98 r.body = [bytes([(x + scale) % 256 for x in r.body[0]])]
99 else:
100 r.body = [chr((ord(x) + scale) % 256) for x in r.body[0]]
101 cherrypy.tools.rotator = cherrypy.Tool('before_finalize', Rotator())
102
103 def stream_handler(next_handler, *args, **kwargs):
104 cherrypy.response.output = o = BytesIO()
105 try:
106 response = next_handler(*args, **kwargs)
107 # Ignore the response and return our accumulated output instead.
108 return o.getvalue()
109 finally:
110 o.close()
111 cherrypy.tools.streamer = cherrypy._cptools.HandlerWrapperTool(stream_handler)
112
113 class Root:
114 def index(self):
115 return "Howdy earth!"
116 index.exposed = True
117
118 def tarfile(self):
119 cherrypy.response.output.write(ntob('I am '))
120 cherrypy.response.output.write(ntob('a tarfile'))
121 tarfile.exposed = True
122 tarfile._cp_config = {'tools.streamer.on': True}
123
124 def euro(self):
125 hooks = list(cherrypy.request.hooks['before_finalize'])
126 hooks.sort()
127 cbnames = [x.callback.__name__ for x in hooks]
128 assert cbnames == ['gzip'], cbnames
129 priorities = [x.priority for x in hooks]
130 assert priorities == [80], priorities
131 yield ntou("Hello,")
132 yield ntou("world")
133 yield europoundUnicode
134 euro.exposed = True
135
136 # Bare hooks
137 def pipe(self):
138 return cherrypy.request.body
139 pipe.exposed = True
140 pipe._cp_config = {'hooks.before_request_body': pipe_body}
141
142 # Multiple decorators; include kwargs just for fun.
143 # Note that rotator must run before gzip.
144 def decorated_euro(self, *vpath):
145 yield ntou("Hello,")
146 yield ntou("world")
147 yield europoundUnicode
148 decorated_euro.exposed = True
149 decorated_euro = tools.gzip(compress_level=6)(decorated_euro)
150 decorated_euro = tools.rotator(scale=3)(decorated_euro)
151
152 root = Root()
153
154
155 class TestType(type):
156 """Metaclass which automatically exposes all functions in each subclass,
157 and adds an instance of the subclass as an attribute of root.
158 """
159 def __init__(cls, name, bases, dct):
160 type.__init__(cls, name, bases, dct)
161 for value in itervalues(dct):
162 if isinstance(value, types.FunctionType):
163 value.exposed = True
164 setattr(root, name.lower(), cls())
165 Test = TestType('Test', (object,), {})
166
167
168 # METHOD ONE:
169 # Declare Tools in _cp_config
170 class Demo(Test):
171
172 _cp_config = {"tools.nadsat.on": True}
173
174 def index(self, id=None):
175 return "A good piece of cherry pie"
176
177 def ended(self, id):
178 return repr(tools.nadsat.ended[id])
179
180 def err(self, id=None):
181 raise ValueError()
182
183 def errinstream(self, id=None):
184 yield "nonconfidential"
185 raise ValueError()
186 yield "confidential"
187
188 # METHOD TWO: decorator using Tool()
189 # We support Python 2.3, but the @-deco syntax would look like this:
190 # @tools.check_access()
191 def restricted(self):
192 return "Welcome!"
193 restricted = myauthtools.check_access()(restricted)
194 userid = restricted
195
196 def err_in_onstart(self):
197 return "success!"
198
199 def stream(self, id=None):
200 for x in xrange(100000000):
201 yield str(x)
202 stream._cp_config = {'response.stream': True}
203
204
205 conf = {
206 # METHOD THREE:
207 # Declare Tools in detached config
208 '/demo': {
209 'tools.numerify.on': True,
210 'tools.numerify.map': {ntob("pie"): ntob("3.14159")},
211 },
212 '/demo/restricted': {
213 'request.show_tracebacks': False,
214 },
215 '/demo/userid': {
216 'request.show_tracebacks': False,
217 'myauth.check_access.default': True,
218 },
219 '/demo/errinstream': {
220 'response.stream': True,
221 },
222 '/demo/err_in_onstart': {
223 # Because this isn't a dict, on_start_resource will error.
224 'tools.numerify.map': "pie->3.14159"
225 },
226 # Combined tools
227 '/euro': {
228 'tools.gzip.on': True,
229 'tools.encode.on': True,
230 },
231 # Priority specified in config
232 '/decorated_euro/subpath': {
233 'tools.gzip.priority': 10,
234 },
235 # Handler wrappers
236 '/tarfile': {'tools.streamer.on': True}
237 }
238 app = cherrypy.tree.mount(root, config=conf)
239 app.request_class.namespaces['myauth'] = myauthtools
240
241 if sys.version_info >= (2, 5):
242 from cherrypy.test import _test_decorators
243 root.tooldecs = _test_decorators.ToolExamples()
244 setup_server = staticmethod(setup_server)
245
247 self.getPage("/demo/?id=1")
248 # If body is "razdrez", then on_end_request is being called too early.
249 self.assertBody("A horrorshow lomtick of cherry 3.14159")
250 # If this fails, then on_end_request isn't being called at all.
251 time.sleep(0.1)
252 self.getPage("/demo/ended/1")
253 self.assertBody("True")
254
255 valerr = '\n raise ValueError()\nValueError'
256 self.getPage("/demo/err?id=3")
257 # If body is "razdrez", then on_end_request is being called too early.
258 self.assertErrorPage(502, pattern=valerr)
259 # If this fails, then on_end_request isn't being called at all.
260 time.sleep(0.1)
261 self.getPage("/demo/ended/3")
262 self.assertBody("True")
263
264 # If body is "razdrez", then on_end_request is being called too early.
265 if (cherrypy.server.protocol_version == "HTTP/1.0" or
266 getattr(cherrypy.server, "using_apache", False)):
267 self.getPage("/demo/errinstream?id=5")
268 # Because this error is raised after the response body has
269 # started, the status should not change to an error status.
270 self.assertStatus("200 OK")
271 self.assertBody("nonconfidential")
272 else:
273 # Because this error is raised after the response body has
274 # started, and because it's chunked output, an error is raised by
275 # the HTTP client when it encounters incomplete output.
276 self.assertRaises((ValueError, IncompleteRead), self.getPage,
277 "/demo/errinstream?id=5")
278 # If this fails, then on_end_request isn't being called at all.
279 time.sleep(0.1)
280 self.getPage("/demo/ended/5")
281 self.assertBody("True")
282
283 # Test the "__call__" technique (compile-time decorator).
284 self.getPage("/demo/restricted")
285 self.assertErrorPage(401)
286
287 # Test compile-time decorator with kwargs from config.
288 self.getPage("/demo/userid")
289 self.assertBody("Welcome!")
290
292 old_timeout = None
293 try:
294 httpserver = cherrypy.server.httpserver
295 old_timeout = httpserver.timeout
296 except (AttributeError, IndexError):
297 return self.skip()
298
299 try:
300 httpserver.timeout = timeout
301
302 # Test that on_end_request is called even if the client drops.
303 self.persistent = True
304 try:
305 conn = self.HTTP_CONN
306 conn.putrequest("GET", "/demo/stream?id=9", skip_host=True)
307 conn.putheader("Host", self.HOST)
308 conn.endheaders()
309 # Skip the rest of the request and close the conn. This will
310 # cause the server's active socket to error, which *should*
311 # result in the request being aborted, and request.close being
312 # called all the way up the stack (including WSGI middleware),
313 # eventually calling our on_end_request hook.
314 finally:
315 self.persistent = False
316 time.sleep(timeout * 2)
317 # Test that the on_end_request hook was called.
318 self.getPage("/demo/ended/9")
319 self.assertBody("True")
320 finally:
321 if old_timeout is not None:
322 httpserver.timeout = old_timeout
323
325 # The 'critical' on_start_resource hook is 'failsafe' (guaranteed
326 # to run even if there are failures in other on_start methods).
327 # This is NOT true of the other hooks.
328 # Here, we have set up a failure in NumerifyTool.numerify_map,
329 # but our 'critical' hook should run and set the error to 502.
330 self.getPage("/demo/err_in_onstart")
331 self.assertErrorPage(502)
332 self.assertInBody("AttributeError: 'str' object has no attribute 'items'")
333
335 expectedResult = (ntou("Hello,world") + europoundUnicode).encode('utf-8')
336 zbuf = BytesIO()
337 zfile = gzip.GzipFile(mode='wb', fileobj=zbuf, compresslevel=9)
338 zfile.write(expectedResult)
339 zfile.close()
340
341 self.getPage("/euro", headers=[("Accept-Encoding", "gzip"),
342 ("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7")])
343 self.assertInBody(zbuf.getvalue()[:3])
344
345 zbuf = BytesIO()
346 zfile = gzip.GzipFile(mode='wb', fileobj=zbuf, compresslevel=6)
347 zfile.write(expectedResult)
348 zfile.close()
349
350 self.getPage("/decorated_euro", headers=[("Accept-Encoding", "gzip")])
351 self.assertInBody(zbuf.getvalue()[:3])
352
353 # This returns a different value because gzip's priority was
354 # lowered in conf, allowing the rotator to run after gzip.
355 # Of course, we don't want breakage in production apps,
356 # but it proves the priority was changed.
357 self.getPage("/decorated_euro/subpath",
358 headers=[("Accept-Encoding", "gzip")])
359 if py3k:
360 self.assertInBody(bytes([(x + 3) % 256 for x in zbuf.getvalue()]))
361 else:
362 self.assertInBody(''.join([chr((ord(x) + 3) % 256) for x in zbuf.getvalue()]))
363
365 content = "bit of a pain in me gulliver"
366 self.getPage("/pipe",
367 headers=[("Content-Length", str(len(content))),
368 ("Content-Type", "text/plain")],
369 method="POST", body=content)
370 self.assertBody(content)
371
375
377 if not sys.version_info >= (2, 5):
378 return self.skip("skipped (Python 2.5+ only)")
379
380 self.getPage('/tooldecs/blah')
381 self.assertHeader('Content-Type', 'application/data')
382
384 # get
385 try:
386 numon = cherrypy.tools.numerify.on
387 except AttributeError:
388 pass
389 else:
390 raise AssertionError("Tool.on did not error as it should have.")
391
392 # set
393 try:
394 cherrypy.tools.numerify.on = True
395 except AttributeError:
396 pass
397 else:
398 raise AssertionError("Tool.on did not error as it should have.")
399
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Mar 25 16:17:51 2014 | http://epydoc.sourceforge.net |