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

Changeset 774

Show
Ignore:
Timestamp:
11/01/05 01:23:34
Author:
fumanchu
Message:

Fix for #357 (Pythonic access to Accept-* request headers). New cptools.getAccept function, which returns a list of AcceptValue? objects (sorted in descending priority). GzipFilter rewritten to use it.

Files:

Legend:

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

    r773 r774  
    186186        raise cherrypy.HTTPError(416, message) 
    187187     
     188    return result 
     189 
     190 
     191class AcceptValue(object): 
     192    """A value (with parameters) from an Accept-* request header.""" 
     193     
     194    def __init__(self, value, params=None): 
     195        self.value = value 
     196        if params is None: 
     197            params = {} 
     198        self.params = params 
     199     
     200    def qvalue(self): 
     201        val = self.params.get("q", "1") 
     202        if isinstance(val, AcceptValue): 
     203            val = val.value 
     204        return float(val) 
     205    qvalue = property(qvalue, doc="The qvalue, or priority, of this value.") 
     206     
     207    def __str__(self): 
     208        p = [";%s=%s" % (k, v) for k, v in self.params.iteritems()] 
     209        return "%s%s" % (self.value, "".join(p)) 
     210     
     211    def __cmp__(self, other): 
     212        # If you sort a list of AcceptValue objects, they will be listed in 
     213        # priority order; that is, the most preferred value will be first. 
     214        diff = cmp(other.qvalue, self.qvalue) 
     215        if diff == 0: 
     216            diff = cmp(str(other), str(self)) 
     217        return diff 
     218 
     219 
     220def getAccept(headername='Accept'): 
     221    """Return a list of AcceptValues from an Accept header, or None.""" 
     222     
     223    r = cherrypy.request.headerMap.get(headername) 
     224    if not r: 
     225        return None 
     226     
     227    result = [] 
     228    for capability in r.split(","): 
     229        # The first "q" parameter (if any) separates the initial 
     230        # parameter(s) (if any) from the accept-params. 
     231        atoms = re.split(r'; *q *=', capability, 1) 
     232        capvalue = atoms.pop(0).strip() 
     233        if atoms: 
     234            qvalue = atoms[0].strip() 
     235            if headername == 'Accept': 
     236                # The qvalue for an Accept header can have extensions. 
     237                atoms = [x.strip() for x in qvalue.split(";")] 
     238                qvalue = atoms.pop(0).strip() 
     239                ext = {} 
     240                for atom in atoms: 
     241                    atom = atom.split("=", 1) 
     242                    key = atom.pop(0).strip() 
     243                    if atom: 
     244                        val = atom[0].strip() 
     245                    else: 
     246                        val = "" 
     247                    ext[key] = val 
     248                qvalue = AcceptValue(qvalue, ext) 
     249            params = {"q": qvalue} 
     250        else: 
     251            params = {} 
     252         
     253        if headername == 'Accept': 
     254            # The media-range may have parameters (before the qvalue). 
     255            atoms = [x.strip() for x in capvalue.split(";")] 
     256            capvalue = atoms.pop(0).strip() 
     257            for atom in atoms: 
     258                atom = atom.split("=", 1) 
     259                key = atom.pop(0).strip() 
     260                if atom: 
     261                    val = atom[0].strip() 
     262                else: 
     263                    val = "" 
     264                params[key] = val 
     265         
     266        result.append(AcceptValue(capvalue, params)) 
     267     
     268    result.sort() 
    188269    return result 
    189270 
  • trunk/cherrypy/lib/filter/gzipfilter.py

    r768 r774  
    1717            return 
    1818         
    19         if not cherrypy.response.body: 
     19        response = cherrypy.response 
     20        if not response.body: 
    2021            # Response body is empty (might be a 304 for instance) 
    2122            return 
    2223         
    23         ct = cherrypy.response.headerMap.get('Content-Type').split(';')[0] 
    24         ae = cherrypy.request.headerMap.get('Accept-Encoding', '') 
    25         if (ct in cherrypy.config.get('gzipFilter.mimeTypeList', ['text/html']) 
    26             and ('gzip' in ae)): 
    27             cherrypy.response.headerMap['Content-Encoding'] = 'gzip' 
     24        def zipit(): 
    2825            # Return a generator that compresses the page 
     26            response.headerMap['Content-Encoding'] = 'gzip' 
    2927            level = cherrypy.config.get('gzipFilter.compresslevel', 9) 
    30             cherrypy.response.body = self.zip_body(cherrypy.response.body, level) 
     28            response.body = self.zip_body(response.body, level) 
     29         
     30        from cherrypy.lib import cptools 
     31        acceptable = cptools.getAccept('Accept-Encoding') 
     32        if acceptable is None: 
     33            # If no Accept-Encoding field is present in a request, 
     34            # the server MAY assume that the client will accept any 
     35            # content coding. In this case, if "identity" is one of 
     36            # the available content-codings, then the server SHOULD use 
     37            # the "identity" content-coding, unless it has additional 
     38            # information that a different content-coding is meaningful 
     39            # to the client. 
     40            return 
     41         
     42        ct = response.headerMap.get('Content-Type').split(';')[0] 
     43        ct = ct in cherrypy.config.get('gzipFilter.mimeTypeList', ['text/html']) 
     44        for coding in acceptable: 
     45            if coding.value == 'identity' and coding.qvalue != 0: 
     46                return 
     47            if coding.value in ('gzip', 'x-gzip'): 
     48                if coding.qvalue == 0: 
     49                    return 
     50                if ct: 
     51                    zipit() 
     52                    return 
     53        cherrypy.HTTPError(406, "identity, gzip").set_response() 
    3154     
    3255    def write_gzip_header(self): 
  • trunk/cherrypy/test/test_core.py

    r770 r774  
    175175        path = os.path.join(os.getcwd(), os.path.dirname(__file__)) 
    176176        return cptools.serveFile(os.path.join(path, "static/index.html")) 
     177 
     178 
     179class Accept(Test): 
     180     
     181    def get_accept(self, headername): 
     182        return "\n".join([str(x) for x in cptools.getAccept(headername)]) 
    177183 
    178184 
     
    572578               "[Errno 2] No such file or directory: 'nonexistent.html'") 
    573579        self.assertInBody(msg) 
    574  
    575580     
    576581    def testRanges(self): 
     
    619624        self.assertHeader("Content-Range", "bytes */14") 
    620625     
     626    def testAccept(self): 
     627        h = [('Accept', 'audio/*; q=0.2, audio/basic')] 
     628        self.getPage("/accept/get_accept?headername=Accept", h) 
     629        self.assertStatus("200 OK") 
     630        self.assertBody("audio/basic\n" 
     631                        "audio/*;q=0.2") 
     632         
     633        h = [('Accept', 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')] 
     634        self.getPage("/accept/get_accept?headername=Accept", h) 
     635        self.assertStatus("200 OK") 
     636        self.assertBody("text/x-c\n" 
     637                        "text/html\n" 
     638                        "text/x-dvi;q=0.8\n" 
     639                        "text/plain;q=0.5") 
     640         
     641        # Test that more specific media ranges get priority. 
     642        # Note that the highest priority will be first in the list. 
     643        h = [('Accept', 'text/*, text/html, text/html;level=1, */*')] 
     644        self.getPage("/accept/get_accept?headername=Accept", h) 
     645        self.assertStatus("200 OK") 
     646        self.assertBody("text/html;level=1\n" 
     647                        "text/html\n" 
     648                        "text/*\n" 
     649                        "*/*") 
     650         
     651        # Test Accept-Charset 
     652        h = [('Accept-Charset', 'iso-8859-5, unicode-1-1;q=0.8')] 
     653        self.getPage("/accept/get_accept?headername=Accept-Charset", h) 
     654        self.assertStatus("200 OK") 
     655        self.assertBody("iso-8859-5\n" 
     656                        "unicode-1-1;q=0.8") 
     657         
     658        # Test Accept-Encoding 
     659        h = [('Accept-Encoding', 'gzip;q=1.0, identity; q=0.5, *;q=0')] 
     660        self.getPage("/accept/get_accept?headername=Accept-Encoding", h) 
     661        self.assertStatus("200 OK") 
     662        self.assertBody("gzip;q=1.0\n" 
     663                        "identity;q=0.5\n" 
     664                        "*;q=0") 
     665         
     666        # Test Accept-Language 
     667        h = [('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')] 
     668        self.getPage("/accept/get_accept?headername=Accept-Language", h) 
     669        self.assertStatus("200 OK") 
     670        self.assertBody("da\n" 
     671                        "en-gb;q=0.8\n" 
     672                        "en;q=0.7") 
     673     
    621674    def testHeaderCaseSensitivity(self): 
    622675        # Tests that each header only appears once, regardless of case. 
  • trunk/cherrypy/test/test_gzip_filter.py

    r768 r774  
    4747        self.assertInBody(zbuf.getvalue()[:3]) 
    4848         
     49        # Test when gzip is denied. 
     50        self.getPage('/', headers=[("Accept-Encoding", "identity")]) 
     51        self.assertBody("Hello, world") 
     52        self.getPage('/', headers=[("Accept-Encoding", "gzip;q=0")]) 
     53        self.assertBody("Hello, world") 
     54        self.getPage('/', headers=[("Accept-Encoding", "*;q=0")]) 
     55        self.assertStatus("406 Not Acceptable") 
     56        self.assertErrorPage(406, "identity, gzip") 
     57         
    4958        # Test for ticket #147 
    5059        helper.webtest.ignored_exceptions.append(IndexError) 

Hosted by WebFaction

Log in as guest/cpguest to create tickets