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 2240 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) 24525 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 chunk247 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!")290292 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_timeout323325 # 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'")333335 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()]))363365 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 375377 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')382384 # 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 Fri Sep 27 14:39:49 2013 | http://epydoc.sourceforge.net |