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

Changeset 679

Show
Ignore:
Timestamp:
09/25/05 18:22:47
Author:
fumanchu
Message:

Fix for #318, #322 and #323.

#318: streaming output is now off by default. This means you may now use "yield" without worrying about pre-determining the status and headers. To enable streaming output you must set the config entry "streamResponse" to True. You should probably also use "yield", since it doesn't make much sense to stream a single chunk.
#322: HTTP/1.0 requests may now stream their output.
#323: moved request.version to response.version. Also, made sure Range headers are not read or written unless response.version >= 1.1.

Files:

Legend:

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

    r650 r679  
    123123        # http://ppewww.ph.gla.ac.uk/~flavell/www/post-redirect.html 
    124124        if status is None: 
    125             if cherrypy.request.version >= "1.1": 
     125            if cherrypy.response.version >= "1.1": 
    126126                status = 303 
    127127            else: 
  • trunk/cherrypy/_cphttpserver.py

    r665 r679  
    8585        cherrypy.response.purge__() 
    8686         
    87         cherrypy.request.multithread = cherrypy.config.get("server.threadPool") > 1 
     87        tp = cherrypy.config.get("server.threadPool") 
     88        cherrypy.request.multithread = (tp > 1) 
    8889        cherrypy.request.multiprocess = False 
    8990        cherrypy.server.request(self.client_address, 
     
    9394                                self.rfile, "http") 
    9495        wfile = self.wfile 
    95         wfile.write("%s %s\r\n" % (self.protocol_version, cherrypy.response.status)) 
     96        wfile.write("%s %s\r\n" % 
     97                    (self.protocol_version, cherrypy.response.status)) 
    9698         
    9799        has_close_conn = False 
  • trunk/cherrypy/_cphttptools.py

    r675 r679  
    301301        # version number to write in the response, so we limit our output 
    302302        # to min(req, server). We want the following output: 
    303         #   request version   server version   response version   features 
    304         # a       1.0              1.0               1.0            1.0 
    305         # b       1.0              1.1               1.1            1.0 
    306         # c       1.1              1.0               1.0            1.0 
    307         # d       1.1              1.1               1.1            1.1 
     303        #     request    server     actual written   supported response 
     304        #     version    version   response version  feature set (resp.v) 
     305        # a     1.0        1.0           1.0                1.0 
     306        # b     1.0        1.1           1.1                1.0 
     307        # c     1.1        1.0           1.0                1.0 
     308        # d     1.1        1.1           1.1                1.1 
    308309        # Notice that, in (b), the response will be "HTTP/1.1" even though 
    309310        # the client only understands 1.0. RFC 2616 10.5.6 says we should 
     
    312313        server_v = cherrypy.config.get("server.protocolVersion", "HTTP/1.0") 
    313314        server_v = Version.from_http(server_v) 
    314         cherrypy.request.version = min(request_v, server_v) 
     315        # cherrypy.response.version should be used to determine whether or 
     316        # not to include a given HTTP/1.1 feature in the response content. 
     317        cherrypy.response.version = min(request_v, server_v) 
     318        # cherrypy.request.version == request.protocol in a Version instance. 
     319        cherrypy.request.version = request_v 
    315320         
    316321        # build a paramMap dictionary from queryString 
     
    352357        request.originalParamList = request.paramList 
    353358         
    354         if cherrypy.request.version >= "1.1": 
     359        if cherrypy.response.version >= "1.1": 
    355360            # All Internet-based HTTP/1.1 servers MUST respond with a 400 
    356361            # (Bad Request) status code to any HTTP/1.1 request message 
     
    577582    checkStatus() 
    578583     
    579     if cherrypy.response.body is None: 
    580         cherrypy.response.body = [] 
    581      
    582     if cherrypy.response.headerMap.get('Content-Length') is None: 
    583         if (cherrypy.request.version < "1.1" or 
    584             # OPTIONS requests MUST include a Content-Length of 0 if no body. 
    585             # Just punt and figure len for all OPTIONS requests. 
    586             cherrypy.request.method == "OPTIONS"): 
    587              
    588             content = ''.join([chunk for chunk in cherrypy.response.body]) 
    589             cherrypy.response.body = [content] 
    590             cherrypy.response.headerMap['Content-Length'] = len(content) 
    591         else: 
    592             try: 
    593                 del cherrypy.response.headerMap['Content-Length'] 
    594             except KeyError: 
    595                 pass 
     584    response = cherrypy.response 
     585    if response.body is None: 
     586        response.body = [] 
     587     
     588    stream = cherrypy.config.get("streamResponse", False) 
     589    # OPTIONS requests MUST include a Content-Length of 0 if no body. 
     590    # Just punt and figure Content-Length for all OPTIONS requests. 
     591    if cherrypy.request.method == "OPTIONS": 
     592        stream = False 
     593     
     594    if stream: 
     595        try: 
     596            del response.headerMap['Content-Length'] 
     597        except KeyError: 
     598            pass 
     599    else: 
     600        # Responses which are not streamed should have a Content-Length, 
     601        # but allow user code to set Content-Length if desired. 
     602        if response.headerMap.get('Content-Length') is None: 
     603            content = ''.join([chunk for chunk in response.body]) 
     604            response.body = [content] 
     605            response.headerMap['Content-Length'] = len(content) 
    596606     
    597607    # For some statuses, Internet Explorer 5+ shows "friendly error messages" 
     
    599609    # Fix this by returning a body over that size (by adding whitespace). 
    600610    # See http://support.microsoft.com/kb/q218155/ 
    601     s = int(cherrypy.response.status.split(" ")[0]) 
     611    s = int(response.status.split(" ")[0]) 
    602612    s = _ie_friendly_error_sizes.get(s, 0) 
    603613    if s: 
     
    605615        # Since we are issuing an HTTP error status, we assume that 
    606616        # the entity is short, and we should just collapse it. 
    607         content = ''.join([chunk for chunk in cherrypy.response.body]) 
    608         cherrypy.response.body = [content] 
     617        content = ''.join([chunk for chunk in response.body]) 
     618        response.body = [content] 
    609619        l = len(content) 
    610620        if l and l < s: 
    611621            # IN ADDITION: the response must be written to IE 
    612622            # in one chunk or it will still get replaced! Bah. 
    613             cherrypy.response.body = [cherrypy.response.body[0] + (" " * (s - l))] 
    614             cherrypy.response.headerMap['Content-Length'] = s 
     623            response.body = [response.body[0] + (" " * (s - l))] 
     624            response.headerMap['Content-Length'] = s 
    615625     
    616626    # Headers 
    617627    headers = [] 
    618     for key, valueList in cherrypy.response.headerMap.iteritems(): 
     628    for key, valueList in response.headerMap.iteritems(): 
    619629        order = _header_order_map.get(key, 3) 
    620630        if not isinstance(valueList, list): 
     
    626636    # ending with the entity-header fields.' 
    627637    headers.sort() 
    628     cherrypy.response.headers = [item[1] for item in headers] 
    629      
    630     cookie = cherrypy.response.simpleCookie.output() 
     638    response.headers = [item[1] for item in headers] 
     639     
     640    cookie = response.simpleCookie.output() 
    631641    if cookie: 
    632642        lines = cookie.split("\n") 
    633643        for line in lines: 
    634644            name, value = line.split(": ", 1) 
    635             cherrypy.response.headers.append((name, value)) 
     645            response.headers.append((name, value)) 
    636646 
    637647 
  • trunk/cherrypy/lib/cptools.py

    r639 r679  
    270270        cherrypy.log("    Found file: %s" % path, "DEBUG") 
    271271     
    272     response.headerMap["Accept-Ranges"] = "bytes" 
    273     r = getRanges(c_len) 
    274     if r: 
    275         if len(r) == 1: 
    276             # Return a single-part response. 
    277             start, stop = r[0] 
    278             r_len = stop - start 
    279             response.status = "206 Partial Content" 
    280             response.headerMap['Content-Range'] = ("bytes %s-%s/%s" % 
    281                                                    (start, stop - 1, c_len)) 
    282             response.headerMap['Content-Length'] = r_len 
    283             bodyfile.seek(start) 
    284             response.body = [bodyfile.read(r_len)] 
     272    # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code 
     273    if cherrypy.response.version >= "1.1": 
     274        response.headerMap["Accept-Ranges"] = "bytes" 
     275        r = getRanges(c_len) 
     276        if r: 
     277            if len(r) == 1: 
     278                # Return a single-part response. 
     279                start, stop = r[0] 
     280                r_len = stop - start 
     281                response.status = "206 Partial Content" 
     282                response.headerMap['Content-Range'] = ("bytes %s-%s/%s" % 
     283                                                       (start, stop - 1, c_len)) 
     284                response.headerMap['Content-Length'] = r_len 
     285                bodyfile.seek(start) 
     286                response.body = [bodyfile.read(r_len)] 
     287            else: 
     288                # Return a multipart/byteranges response. 
     289                response.status = "206 Partial Content" 
     290                boundary = mimetools.choose_boundary() 
     291                ct = "multipart/byteranges; boundary=%s" % boundary 
     292                response.headerMap['Content-Type'] = ct 
     293##                del response.headerMap['Content-Length'] 
     294                 
     295                def fileRanges(): 
     296                    for start, stop in r: 
     297                        yield "--" + boundary 
     298                        yield "\nContent-type: %s" % contentType 
     299                        yield ("\nContent-range: bytes %s-%s/%s\n\n" 
     300                               % (start, stop - 1, c_len)) 
     301                        bodyfile.seek(start) 
     302                        yield bodyfile.read((stop + 1) - start) 
     303                        yield "\n" 
     304                    # Final boundary 
     305                    yield "--" + boundary 
     306                response.body = fileRanges() 
    285307        else: 
    286             # Return a multipart/byteranges response. 
    287             response.status = "206 Partial Content" 
    288             boundary = mimetools.choose_boundary() 
    289             ct = "multipart/byteranges; boundary=%s" % boundary 
    290             response.headerMap['Content-Type'] = ct 
    291             del response.headerMap['Content-Length'] 
    292              
    293             def fileRanges(): 
    294                 for start, stop in r: 
    295                     yield "--" + boundary 
    296                     yield "\nContent-type: %s" % contentType 
    297                     yield ("\nContent-range: bytes %s-%s/%s\n\n" 
    298                            % (start, stop - 1, c_len)) 
    299                     bodyfile.seek(start) 
    300                     yield bodyfile.read((stop + 1) - start) 
    301                     yield "\n" 
    302                 # Final boundary 
    303                 yield "--" + boundary 
    304             response.body = fileRanges() 
    305      
     308            response.headerMap['Content-Length'] = c_len 
     309            response.body = fileGenerator(bodyfile) 
    306310    else: 
    307311        response.headerMap['Content-Length'] = c_len 
  • trunk/cherrypy/test/helper.py

    r665 r679  
    120120                self.body.append(chunk) 
    121121        except: 
    122             if cherrypy.config.get("server.protocolVersion") == "HTTP/1.0"
     122            if cherrypy.config.get("streamResponse", False)
    123123                # Pass the error through 
    124124                raise 
  • trunk/cherrypy/test/test.py

    r660 r679  
    252252         
    253253        import cherrypy 
    254         print "Python version used to run this test script:", sys.version.split()[0] 
     254        v = sys.version.split()[0] 
     255        print "Python version used to run this test script:", v 
    255256        print "CherryPy version", cherrypy.__version__ 
    256257        print 
  • trunk/cherrypy/test/test_core.py

    r665 r679  
    169169        raise ValueError() 
    170170     
    171     def page_http_1_1(self): 
    172         cherrypy.response.headerMap["Content-Length"] = 39 
    173         def inner(): 
    174             yield "hello" 
    175             raise ValueError() 
    176             yield "very oops" 
    177         return inner() 
     171    def page_streamed(self): 
     172        yield "hello" 
     173        raise ValueError() 
     174        yield "very oops" 
    178175     
    179176    def cause_err_in_finalize(self): 
     
    296293        'server.logTracebacks': True, 
    297294    }, 
     295    '/error/page_streamed': { 
     296        'streamResponse': True, 
     297    }, 
    298298}) 
    299299 
     
    507507            self.getPage("/error/page_method") 
    508508            self.assertErrorPage(500, valerr) 
    509  
     509             
     510            self.getPage("/error/page_yield") 
     511            self.assertErrorPage(500, valerr) 
     512             
    510513            import cherrypy 
    511             proto = cherrypy.config.get("server.protocolVersion", "HTTP/1.0") 
    512             if proto == "HTTP/1.1": 
    513                 valerr = r'Unrecoverable error in the server.$' 
    514             self.getPage("/error/page_yield") 
    515             self.assertMatchesBody(valerr) 
    516              
    517             if cherrypy._httpserver is None and proto == "HTTP/1.0": 
    518                 self.assertRaises(ValueError, self.getPage, "/error/page_http_1_1") 
     514            # streamResponse should be True for this path. 
     515            if cherrypy._httpserver is None: 
     516                self.assertRaises(ValueError, self.getPage, 
     517                                  "/error/page_streamed") 
    519518            else: 
    520                 self.getPage("/error/page_http_1_1") 
     519                self.getPage("/error/page_streamed") 
    521520                # Because this error is raised after the response body has 
    522521                # started, the status should not change to an error status. 
     
    568567--%s""" % (boundary, boundary, boundary) 
    569568        self.assertBody(expected_body) 
    570         self.assertNoHeader("Content-Length") 
     569        self.assertHeader("Content-Length") 
    571570         
    572571        # Test "416 Requested Range Not Satisfiable" 
  • trunk/cherrypy/test/test_gzip_filter.py

    r639 r679  
    4141        yield "Here be dragons" 
    4242    noshow.exposed = True 
     43     
     44    def noshow_stream(self): 
     45        # Test for ticket #147, where yield showed no exceptions (content- 
     46        # encoding was still gzip even though traceback wasn't zipped). 
     47        raise IndexError() 
     48        yield "Here be dragons" 
     49    noshow_stream.exposed = True 
    4350 
    4451cherrypy.root = Root() 
    4552cherrypy.config.update({ 
    46         'server.logToScreen': False, 
    47         'server.environment': 'production', 
    48         'server.showTracebacks': True, 
    49         'gzipFilter.on': True, 
     53    'global': {'server.logToScreen': False, 
     54               'server.environment': 'production', 
     55               'server.showTracebacks': True, 
     56               'gzipFilter.on': True, 
     57               }, 
     58    '/noshow_stream': {'streamResponse': True}, 
    5059}) 
    5160 
     
    7079        try: 
    7180            self.getPage('/noshow', headers=[("Accept-Encoding", "gzip")]) 
     81            self.assertNoHeader('Content-Encoding') 
     82            self.assertStatus('500 Internal error') 
     83            self.assertErrorPage(500, "IndexError\n") 
    7284             
    73             import cherrypy 
    74             proto = cherrypy.config.get("server.protocolVersion", "HTTP/1.0") 
    75             if proto == "HTTP/1.1": 
    76                 # In this case, there's nothing we can do to deliver a 
    77                 # readable page, since 1) the gzip header is already set, 
    78                 # and 2) we may have already written some of the body. 
    79                 # The fix is to not use yield when using HTTP/1.1 and gzip. 
     85            # In this case, there's nothing we can do to deliver a 
     86            # readable page, since 1) the gzip header is already set, 
     87            # and 2) we may have already written some of the body. 
     88            # The fix is to never stream yields when using gzip. 
     89            if cherrypy._httpserver is None: 
     90                self.assertRaises(IndexError, self.getPage, 
     91                                  '/noshow_stream', 
     92                                  [("Accept-Encoding", "gzip")]) 
     93            else: 
     94                self.getPage('/noshow_stream', 
     95                             headers=[("Accept-Encoding", "gzip")]) 
    8096                self.assertHeader('Content-Encoding', 'gzip') 
    8197                self.assertMatchesBody(r"Unrecoverable error in the server.$") 
    82             else: 
    83                 self.assertNoHeader('Content-Encoding') 
    84                 self.assertStatus('500 Internal error') 
    85                 self.assertErrorPage(500, "IndexError\n") 
    8698        finally: 
    8799            helper.webtest.ignored_exceptions.pop() 

Hosted by WebFaction

Log in as guest/cpguest to create tickets