Changeset 1422
- Timestamp:
- 11/02/06 18:49:48
- Files:
-
- trunk/cherrypy/_cptools.py (modified) (1 diff)
- trunk/cherrypy/lib/cptools.py (modified) (1 diff)
- trunk/cherrypy/test/test_misc_tools.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/_cptools.py
r1420 r1422 357 357 _d.trailing_slash = Tool('before_handler', cptools.trailing_slash) 358 358 _d.flatten = Tool('before_finalize', cptools.flatten) 359 _d.accept = Tool('on_start_resource', cptools.accept) 359 360 360 361 del _d, cptools, encoding, auth, static, tidy trunk/cherrypy/lib/cptools.py
r1415 r1422 349 349 response.body = flattener(response.body) 350 350 351 352 def accept(media=None): 353 """Return the client's preferred media-type (from the given Content-Types). 354 355 If 'media' is None (the default), no test will be performed. 356 357 If 'media' is provided, it should be the Content-Type value (as a string) 358 or values (as a list or tuple of strings) which the current request 359 can emit. The client's acceptable media ranges (as declared in the 360 Accept request header) will be matched in order to these Content-Type 361 values; the first such string is returned. That is, the return value 362 will always be one of the strings provided in the 'media' arg (or None 363 if 'media' is None). 364 365 If no match is found, then HTTPError 406 (Not Acceptable) is raised. 366 Note that most web browsers send */* as a (low-quality) acceptable 367 media range, which should match any Content-Type. In addition, "...if 368 no Accept header field is present, then it is assumed that the client 369 accepts all media types." 370 371 Matching types are checked in order of client preference first, 372 and then in the order of the given 'media' values. 373 374 Note that this function does not honor accept-params (other than "q"). 375 """ 376 if not media: 377 return 378 if isinstance(media, basestring): 379 media = [media] 380 381 # Parse the Accept request header, and try to match one 382 # of the requested media-ranges (in order of preference). 383 ranges = cherrypy.request.headers.elements('Accept') 384 if not ranges: 385 # Any media type is acceptable. 386 return media[0] 387 else: 388 # Note that 'ranges' is sorted in order of preference 389 for element in ranges: 390 if element.qvalue > 0: 391 if element.value == "*/*": 392 # Matches any type or subtype 393 return media[0] 394 elif element.value.endswith("/*"): 395 # Matches any subtype 396 mtype = element.value[:-1] # Keep the slash 397 for m in media: 398 if m.startswith(mtype): 399 return m 400 else: 401 # Matches exact value 402 if element.value in media: 403 return element.value 404 405 # No suitable media-range found. 406 ah = cherrypy.request.headers.get('Accept') 407 if ah is None: 408 msg = "Your client did not send an Accept header." 409 else: 410 msg = "Your client sent this Accept header: %s." % ah 411 msg += (" But this resource only emits these media types: %s." % 412 ", ".join(media)) 413 raise cherrypy.HTTPError(406, msg) 414 trunk/cherrypy/test/test_misc_tools.py
r1330 r1422 23 23 } 24 24 25 26 class Accept: 27 _cp_config = {'tools.accept.on': True} 28 29 def index(self): 30 return '<a href="feed">Atom feed</a>' 31 index.exposed = True 32 33 # In Python 2.4+, we could use a decorator instead: 34 # @tools.accept('application/atom+xml') 35 def feed(self): 36 return """<?xml version="1.0" encoding="utf-8"?> 37 <feed xmlns="http://www.w3.org/2005/Atom"> 38 <title>Unknown Blog</title> 39 </feed>""" 40 feed.exposed = True 41 feed._cp_config = {'tools.accept.media': 'application/atom+xml'} 42 43 def select(self): 44 # We could also write this: mtype = cherrypy.lib.accept.accept(...) 45 mtype = tools.accept.callable(['text/html', 'text/plain']) 46 if mtype == 'text/html': 47 return "<h2>Page Title</h2>" 48 else: 49 return "PAGE TITLE" 50 select.exposed = True 51 25 52 class Referer: 26 53 def accept(self): … … 39 66 root = Root() 40 67 root.referer = Referer() 68 root.accept = Accept() 41 69 cherrypy.tree.mount(root, config=conf) 42 70 cherrypy.config.update({'environment': 'test_suite'}) … … 79 107 80 108 109 class AcceptTest(helper.CPWebCase): 110 111 def test_Accept_Tool(self): 112 # Test with no header provided 113 self.getPage('/accept/feed') 114 self.assertStatus(200) 115 self.assertInBody('<title>Unknown Blog</title>') 116 117 # Specify exact media type 118 self.getPage('/accept/feed', headers=[('Accept', 'application/atom+xml')]) 119 self.assertStatus(200) 120 self.assertInBody('<title>Unknown Blog</title>') 121 122 # Specify matching media range 123 self.getPage('/accept/feed', headers=[('Accept', 'application/*')]) 124 self.assertStatus(200) 125 self.assertInBody('<title>Unknown Blog</title>') 126 127 # Specify all media ranges 128 self.getPage('/accept/feed', headers=[('Accept', '*/*')]) 129 self.assertStatus(200) 130 self.assertInBody('<title>Unknown Blog</title>') 131 132 # Specify unacceptable media types 133 self.getPage('/accept/feed', headers=[('Accept', 'text/html')]) 134 self.assertErrorPage(406, 135 "Your client sent this Accept header: text/html. " 136 "But this resource only emits these media types: " 137 "application/atom+xml.") 138 139 # Test resource where tool is 'on' but media is None (not set). 140 self.getPage('/accept/') 141 self.assertStatus(200) 142 self.assertBody('<a href="feed">Atom feed</a>') 143 144 def test_accept_selection(self): 145 # Try both our expected media types 146 self.getPage('/accept/select', [('Accept', 'text/html')]) 147 self.assertStatus(200) 148 self.assertBody('<h2>Page Title</h2>') 149 self.getPage('/accept/select', [('Accept', 'text/plain')]) 150 self.assertStatus(200) 151 self.assertBody('PAGE TITLE') 152 self.getPage('/accept/select', [('Accept', 'text/plain, text/*;q=0.5')]) 153 self.assertStatus(200) 154 self.assertBody('PAGE TITLE') 155 156 # text/* and */* should prefer text/html since it comes first 157 # in our 'media' argument to tools.accept 158 self.getPage('/accept/select', [('Accept', 'text/*')]) 159 self.assertStatus(200) 160 self.assertBody('<h2>Page Title</h2>') 161 self.getPage('/accept/select', [('Accept', '*/*')]) 162 self.assertStatus(200) 163 self.assertBody('<h2>Page Title</h2>') 164 165 # Try unacceptable media types 166 self.getPage('/accept/select', [('Accept', 'application/xml')]) 167 self.assertErrorPage(406, 168 "Your client sent this Accept header: application/xml. " 169 "But this resource only emits these media types: " 170 "text/html, text/plain.") 171 172 173 81 174 if __name__ == "__main__": 82 175 setup_server()

