Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 1422

Show
Ignore:
Timestamp:
11/02/06 18:49:48
Author:
fumanchu
Message:

New tools.accept(media). See test_misc_tools.py for usage.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/_cptools.py

    r1420 r1422  
    357357_d.trailing_slash = Tool('before_handler', cptools.trailing_slash) 
    358358_d.flatten = Tool('before_finalize', cptools.flatten) 
     359_d.accept = Tool('on_start_resource', cptools.accept) 
    359360 
    360361del _d, cptools, encoding, auth, static, tidy 
  • trunk/cherrypy/lib/cptools.py

    r1415 r1422  
    349349    response.body = flattener(response.body) 
    350350 
     351 
     352def 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  
    2323            } 
    2424     
     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     
    2552    class Referer: 
    2653        def accept(self): 
     
    3966    root = Root() 
    4067    root.referer = Referer() 
     68    root.accept = Accept() 
    4169    cherrypy.tree.mount(root, config=conf) 
    4270    cherrypy.config.update({'environment': 'test_suite'}) 
     
    79107 
    80108 
     109class 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 
    81174if __name__ == "__main__": 
    82175    setup_server() 

Hosted by WebFaction

Log in as guest/cpguest to create tickets