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

Changeset 1978

Show
Ignore:
Timestamp:
06/07/08 16:10:53
Author:
fumanchu
Message:

Fix for #820 (start_response with exc_info raises exception even if no output was sent yet). This also fixes related issues in cpwsgi and error output in general.

Files:

Legend:

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

    r1969 r1978  
    7575     
    7676    def setapp(self): 
    77         # Stage 1: by whatever means necessary, obtain a status, header 
    78         #          set and body, with an optional exception object. 
    7977        try: 
    8078            self.request = self.get_request() 
    8179            s, h, b = self.get_response() 
    82             exc = None 
     80            self.iter_response = iter(b) 
     81            self.start_response(s, h) 
    8382        except self.throws: 
    8483            self.close() 
     
    9998                tb = "" 
    10099            s, h, b = _cperror.bare_error(tb) 
    101             exc = _sys.exc_info() 
    102          
    103         self.iter_response = iter(b) 
    104          
    105         # Stage 2: now that we have a status, header set, and body, 
    106         #          finish the WSGI conversation by calling start_response. 
    107         try: 
    108             self.start_response(s, h, exc) 
    109         except self.throws: 
    110             self.close() 
    111             raise 
    112         except: 
    113             if getattr(self.request, "throw_errors", False): 
     100            self.iter_response = iter(b) 
     101             
     102            try: 
     103                self.start_response(s, h, _sys.exc_info()) 
     104            except: 
     105                # "The application must not trap any exceptions raised by 
     106                # start_response, if it called start_response with exc_info. 
     107                # Instead, it should allow such exceptions to propagate 
     108                # back to the server or gateway." 
     109                # But we still log and call close() to clean up ourselves. 
     110                _cherrypy.log(traceback=True, severity=40) 
    114111                self.close() 
    115112                raise 
    116              
    117             _cherrypy.log(traceback=True, severity=40) 
    118             self.close() 
    119              
    120             # CherryPy test suite expects bare_error body to be output, 
    121             # so don't call start_response (which, according to PEP 333, 
    122             # may raise its own error at that point). 
    123             s, h, b = _cperror.bare_error() 
    124             self.iter_response = iter(b) 
    125113     
    126114    def iredirect(self, path, query_string): 
     
    194182                raise 
    195183             
    196             _cherrypy.log(traceback=True, severity=40
    197              
    198             # CherryPy test suite expects bare_error body to be output, 
    199             # so don't call start_response (which, according to PEP 333, 
    200             # may raise its own error at that point). 
    201             s, h, b = _cperror.bare_error(
     184            tb = _cperror.format_exc(
     185            _cherrypy.log(tb, severity=40) 
     186            if not getattr(self.request, "show_tracebacks", True): 
     187                tb = "" 
     188            s, h, b = _cperror.bare_error(tb) 
     189            # Empty our iterable (so future calls raise StopIteration
    202190            self.iter_response = iter([]) 
     191             
     192            try: 
     193                self.start_response(s, h, _sys.exc_info()) 
     194            except: 
     195                # "The application must not trap any exceptions raised by 
     196                # start_response, if it called start_response with exc_info. 
     197                # Instead, it should allow such exceptions to propagate 
     198                # back to the server or gateway." 
     199                # But we still log and call close() to clean up ourselves. 
     200                _cherrypy.log(traceback=True, severity=40) 
     201                self.close() 
     202                raise 
     203             
    203204            return "".join(b) 
    204205     
  • trunk/cherrypy/test/test_core.py

    r1949 r1978  
    718718            self.assertErrorPage(500, pattern=valerr) 
    719719             
    720             self.getPage("/error/page_streamed") 
    721             # Because this error is raised after the response body has 
    722             # started, the status should not change to an error status. 
    723             self.assertStatus(200) 
    724             self.assertBody("word upUnrecoverable error in the server.") 
     720            if cherrypy.server.protocol_version == "HTTP/1.0": 
     721                self.getPage("/error/page_streamed") 
     722                # Because this error is raised after the response body has 
     723                # started, the status should not change to an error status. 
     724                self.assertStatus(200) 
     725                self.assertBody("word up") 
     726            else: 
     727                # Under HTTP/1.1, the chunked transfer-coding is used. 
     728                # The HTTP client will choke when the output is incomplete. 
     729                self.assertRaises(ValueError, self.getPage, 
     730                                  "/error/page_streamed") 
    725731             
    726732            # No traceback should be present 
  • trunk/cherrypy/test/test_encoding.py

    r1832 r1978  
    145145        # and 2) we may have already written some of the body. 
    146146        # The fix is to never stream yields when using gzip. 
    147         self.getPage('/gzip/noshow_stream', 
    148                      headers=[("Accept-Encoding", "gzip")]) 
    149         self.assertHeader('Content-Encoding', 'gzip') 
    150         self.assertMatchesBody(r"Unrecoverable error in the server.$") 
     147        if cherrypy.server.protocol_version == "HTTP/1.0": 
     148            self.getPage('/gzip/noshow_stream', 
     149                         headers=[("Accept-Encoding", "gzip")]) 
     150            self.assertHeader('Content-Encoding', 'gzip') 
     151            self.assertInBody('\x1f\x8b\x08\x00') 
     152        else: 
     153            # The wsgiserver will simply stop sending data, and the HTTP client 
     154            # will error due to an incomplete chunk-encoded stream. 
     155            self.assertRaises(ValueError, self.getPage, '/gzip/noshow_stream', 
     156                              headers=[("Accept-Encoding", "gzip")]) 
    151157 
    152158 
  • trunk/cherrypy/test/test_tools.py

    r1960 r1978  
    171171         
    172172        def errinstream(self, id=None): 
     173            yield "nonconfidential" 
    173174            raise ValueError() 
    174175            yield "confidential" 
     
    256257         
    257258        # If body is "razdrez", then on_end_request is being called too early. 
    258         self.getPage("/demo/errinstream?id=5") 
    259         # Because this error is raised after the response body has 
    260         # started, the status should not change to an error status. 
    261         self.assertStatus("200 OK") 
    262         self.assertBody("Unrecoverable error in the server.") 
     259        if cherrypy.server.protocol_version == "HTTP/1.0": 
     260            self.getPage("/demo/errinstream?id=5") 
     261            # Because this error is raised after the response body has 
     262            # started, the status should not change to an error status. 
     263            self.assertStatus("200 OK") 
     264            self.assertBody("nonconfidential") 
     265        else: 
     266            # Because this error is raised after the response body has 
     267            # started, and because it's chunked output, an error is raised by 
     268            # the HTTP client when it encounters incomplete output. 
     269            self.assertRaises(ValueError, self.getPage, 
     270                              "/demo/errinstream?id=5") 
    263271        # If this fails, then on_end_request isn't being called at all. 
    264272        time.sleep(0.1) 
  • trunk/cherrypy/wsgiserver/__init__.py

    r1971 r1978  
    538538            self._respond() 
    539539        except MaxSizeExceeded: 
    540             self.simple_response("413 Request Entity Too Large") 
     540            if not self.sent_headers: 
     541                self.simple_response("413 Request Entity Too Large") 
    541542            return 
    542543     
     
    587588    def start_response(self, status, headers, exc_info = None): 
    588589        """WSGI callable to begin the HTTP response.""" 
    589         if self.started_response: 
    590             if not exc_info: 
    591                 raise AssertionError("WSGI start_response called a second " 
    592                                      "time with no exc_info.") 
    593             else: 
    594                 try: 
    595                     raise exc_info[0], exc_info[1], exc_info[2] 
    596                 finally: 
    597                     exc_info = None 
     590        # "The application may call start_response more than once, 
     591        # if and only if the exc_info argument is provided." 
     592        if self.started_response and not exc_info: 
     593            raise AssertionError("WSGI start_response called a second " 
     594                                 "time with no exc_info.") 
     595         
     596        # "if exc_info is provided, and the HTTP headers have already been 
     597        # sent, start_response must raise an error, and should raise the 
     598        # exc_info tuple." 
     599        if self.sent_headers: 
     600            try: 
     601                raise exc_info[0], exc_info[1], exc_info[2] 
     602            finally: 
     603                exc_info = None 
     604         
    598605        self.started_response = True 
    599606        self.status = status 
     
    980987            errnum = e.args[0] 
    981988            if errnum == 'timed out': 
    982                 if req
     989                if req and not req.sent_headers
    983990                    req.simple_response("408 Request Timeout") 
    984991            elif errnum not in socket_errors_to_ignore: 
    985                 if req
     992                if req and not req.sent_headers
    986993                    req.simple_response("500 Internal Server Error", 
    987994                                        format_exc()) 
     
    9951002            # Unwrap our wfile 
    9961003            req.wfile = CP_fileobject(self.socket, "wb", -1) 
    997             req.simple_response("400 Bad Request", 
    998                                 "The client sent a plain HTTP request, but " 
    999                                 "this server only speaks HTTPS on this port.") 
     1004            if req and not req.sent_headers: 
     1005                req.simple_response("400 Bad Request", 
     1006                    "The client sent a plain HTTP request, but " 
     1007                    "this server only speaks HTTPS on this port.") 
    10001008        except Exception, e: 
    1001             if req
     1009            if req and not req.sent_headers
    10021010                req.simple_response("500 Internal Server Error", format_exc()) 
    10031011     

Hosted by WebFaction

Log in as guest/cpguest to create tickets