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

Changeset 1465

Show
Ignore:
Timestamp:
12/02/06 13:50:55
Author:
fumanchu
Message:

Fix for #612 (Mechanism to set Content-Length on streamed responses).

Files:

Legend:

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

    r1449 r1465  
    536536        headers = self.headers 
    537537        if self.stream: 
    538             headers.pop('Content-Length', None) 
     538            if dict.get(headers, 'Content-Length') is None: 
     539                dict.pop(headers, 'Content-Length', None) 
    539540        elif code < 200 or code in (204, 304): 
    540541            # "All 1xx (informational), 204 (no content), 
    541542            # and 304 (not modified) responses MUST NOT 
    542543            # include a message-body." 
    543             headers.pop('Content-Length', None) 
     544            dict.pop(headers, 'Content-Length', None) 
    544545            self.body = "" 
    545546        else: 
  • trunk/cherrypy/test/test_conn.py

    r1464 r1465  
    2929        hello.exposed = True 
    3030         
    31         def stream(self): 
    32             for x in xrange(10): 
    33                 yield str(x) 
     31        def stream(self, set_cl=False): 
     32            if set_cl: 
     33                cherrypy.response.headers['Content-Length'] = 10 
     34             
     35            def content(): 
     36                for x in xrange(10): 
     37                    yield str(x) 
     38             
     39            return content() 
    3440        stream.exposed = True 
    3541        stream._cp_config = {'response.stream': True} 
     
    5763class ConnectionTests(helper.CPWebCase): 
    5864     
    59     def test_HTTP11(self): 
    60         if cherrypy.server.protocol_version != "HTTP/1.1": 
    61             print "skipped ", 
    62             return 
    63          
    64         self.PROTOCOL = "HTTP/1.1" 
    65          
    66         # Set our HTTP_CONN to an instance so it persists between requests. 
     65    def connect_persistent(self, auto_open=False): 
     66        """Set our HTTP_CONN to an instance so it persists between requests.""" 
    6767        if self.scheme == "https": 
    6868            self.HTTP_CONN = httplib.HTTPSConnection(self.HOST, self.PORT) 
    6969        else: 
    7070            self.HTTP_CONN = httplib.HTTPConnection(self.HOST, self.PORT) 
    71         # Don't automatically re-connect 
    72         self.HTTP_CONN.auto_open = False 
     71        # Automatically re-connect? 
     72        self.HTTP_CONN.auto_open = auto_open 
    7373        self.HTTP_CONN.connect() 
     74        return self.HTTP_CONN 
     75     
     76    def test_HTTP11(self): 
     77        if cherrypy.server.protocol_version != "HTTP/1.1": 
     78            print "skipped ", 
     79            return 
     80         
     81        self.PROTOCOL = "HTTP/1.1" 
     82         
     83        self.connect_persistent() 
    7484         
    7585        # Make the first request and assert there's no "Connection: close". 
     
    8595        self.assertNoHeader("Connection") 
    8696         
    87         # Make another, streamed request on the same connection. 
    88         self.getPage("/stream") 
    89         self.assertStatus('200 OK') 
    90         self.assertBody('0123456789') 
    91         self.assertNoHeader("Content-Length") 
    92         # Streamed output will either close the connection, or use 
    93         # chunked encoding, to determine transfer-length. 
    94         chunked_response = False 
    95         for k, v in self.headers: 
    96             if k.lower() == "transfer-encoding": 
    97                 if str(v) == "chunked": 
    98                     chunked_response = True 
    99         if not chunked_response: 
    100             self.assertHeader("Connection", "close") 
    101              
    102             # Make another request on the same connection, which should error. 
    103             self.assertRaises(httplib.NotConnected, self.getPage, "/") 
    104          
    10597        # Test client-side close. 
    106         if self.scheme == "https": 
    107             self.HTTP_CONN = httplib.HTTPSConnection(self.HOST, self.PORT) 
    108         else: 
    109             self.HTTP_CONN = httplib.HTTPConnection(self.HOST, self.PORT) 
    110         self.HTTP_CONN.auto_open = False 
    111         self.HTTP_CONN.connect() 
    11298        self.getPage("/page2", headers=[("Connection", "close")]) 
    11399        self.assertStatus('200 OK') 
     
    117103        # Make another request on the same connection, which should error. 
    118104        self.assertRaises(httplib.NotConnected, self.getPage, "/") 
     105     
     106    def test_Streaming_no_len(self): 
     107        self._streaming(set_cl=False) 
     108     
     109    def test_Streaming_with_len(self): 
     110        self._streaming(set_cl=True) 
     111     
     112    def _streaming(self, set_cl): 
     113        if cherrypy.server.protocol_version != "HTTP/1.1": 
     114            print "skipped ", 
     115            return 
     116         
     117        self.PROTOCOL = "HTTP/1.1" 
     118         
     119        self.connect_persistent() 
     120         
     121        # Make the first request and assert there's no "Connection: close". 
     122        self.getPage("/") 
     123        self.assertStatus('200 OK') 
     124        self.assertBody(pov) 
     125        self.assertNoHeader("Connection") 
     126         
     127        # Make another, streamed request on the same connection. 
     128        if set_cl: 
     129            # When a Content-Length is provided, the content should stream 
     130            # without closing the connection. 
     131            self.getPage("/stream?set_cl=Yes") 
     132            self.assertHeader("Content-Length") 
     133            self.assertNoHeader("Connection", "close") 
     134            self.assertNoHeader("Transfer-Encoding") 
     135        else: 
     136            # When no Content-Length response header is provided, 
     137            # streamed output will either close the connection, or use 
     138            # chunked encoding, to determine transfer-length. 
     139            self.getPage("/stream") 
     140            self.assertNoHeader("Content-Length") 
     141             
     142            chunked_response = False 
     143            for k, v in self.headers: 
     144                if k.lower() == "transfer-encoding": 
     145                    if str(v) == "chunked": 
     146                        chunked_response = True 
     147             
     148            if chunked_response: 
     149                self.assertNoHeader("Connection", "close") 
     150            else: 
     151                self.assertHeader("Connection", "close") 
     152                 
     153                # Make another request on the same connection, which should error. 
     154                self.assertRaises(httplib.NotConnected, self.getPage, "/") 
     155         
     156        self.assertStatus('200 OK') 
     157        self.assertBody('0123456789') 
    119158     
    120159    def test_HTTP11_Timeout(self): 
     
    136175             
    137176            # Make an initial request 
    138             if self.scheme == "https": 
    139                 conn = httplib.HTTPSConnection(self.HOST, self.PORT) 
    140             else: 
    141                 conn = httplib.HTTPConnection(self.HOST, self.PORT) 
    142             conn.auto_open = False 
    143             conn.connect() 
     177            conn = self.connect_persistent() 
    144178            conn.putrequest("GET", "/", skip_host=True) 
    145179            conn.putheader("Host", self.HOST) 
     
    184218             
    185219            # Make another request on a new socket, which should work 
    186             if self.scheme == "https": 
    187                 conn = httplib.HTTPSConnection(self.HOST, self.PORT) 
    188             else: 
    189                 conn = httplib.HTTPConnection(self.HOST, self.PORT) 
    190             conn.auto_open = False 
    191             conn.connect() 
     220            conn = self.connect_persistent() 
    192221            conn.putrequest("GET", "/", skip_host=True) 
    193222            conn.putheader("Host", self.HOST) 
     
    210239         
    211240        # Test pipelining. httplib doesn't support this directly. 
    212         if self.scheme == "https": 
    213             conn = httplib.HTTPSConnection(self.HOST, self.PORT) 
    214         else: 
    215             conn = httplib.HTTPConnection(self.HOST, self.PORT) 
    216         conn.auto_open = False 
    217         conn.connect() 
     241        conn = self.connect_persistent() 
    218242         
    219243        # Put request 1 
     
    251275        self.PROTOCOL = "HTTP/1.1" 
    252276         
    253         if self.scheme == "https": 
    254             conn = httplib.HTTPSConnection(self.HOST, self.PORT) 
    255         else: 
    256             conn = httplib.HTTPConnection(self.HOST, self.PORT) 
    257         conn.auto_open = False 
    258         conn.connect() 
     277        conn = self.connect_persistent() 
    259278         
    260279        # Try a page without an Expect request header first. 
     
    307326         
    308327        # Set our HTTP_CONN to an instance so it persists between requests. 
    309         if self.scheme == "https": 
    310             self.HTTP_CONN = httplib.HTTPSConnection(self.HOST, self.PORT) 
    311         else: 
    312             self.HTTP_CONN = httplib.HTTPConnection(self.HOST, self.PORT) 
    313         # Don't automatically re-connect 
    314         self.HTTP_CONN.auto_open = False 
    315         self.HTTP_CONN.connect() 
     328        self.connect_persistent() 
    316329         
    317330        # Make the first request and assert there's no "Connection: close". 
     
    402415         
    403416        # Test a keep-alive HTTP/1.0 request. 
    404         if self.scheme == "https": 
    405             self.HTTP_CONN = httplib.HTTPSConnection(self.HOST, self.PORT) 
    406         else: 
    407             self.HTTP_CONN = httplib.HTTPConnection(self.HOST, self.PORT) 
    408         self.HTTP_CONN.auto_open = False 
    409         self.HTTP_CONN.connect() 
     417        self.connect_persistent() 
    410418         
    411419        self.getPage("/page3", headers=[("Connection", "Keep-Alive")]) 
  • trunk/cherrypy/wsgiserver.py

    r1464 r1465  
    6262        self.sent_headers = False 
    6363        self.close_connection = False 
     64        self.chunked_write = False 
    6465     
    6566    def parse_request(self): 
     
    276277     
    277278    def respond(self): 
     279        wfile = self.connection.wfile 
    278280        response = self.wsgi_app(self.environ, self.start_response) 
    279281        try: 
    280282            for line in response: 
    281                 self.write(line) 
     283                if not self.sent_headers: 
     284                    self.sent_headers = True 
     285                    self.send_headers() 
     286                if self.chunked_write: 
     287                    wfile.write(hex(len(line))[2:]) 
     288                    wfile.write("\r\n") 
     289                    wfile.write(line) 
     290                    wfile.write("\r\n") 
     291                else: 
     292                    wfile.write(line) 
     293                wfile.flush() 
    282294        finally: 
    283295            if hasattr(response, "close"): 
     
    287299            self.sent_headers = True 
    288300            self.send_headers() 
     301        if self.chunked_write: 
     302            wfile.write("0\r\n\r\n") 
     303            wfile.flush() 
    289304     
    290305    def simple_response(self, status, msg=""): 
     
    327342     
    328343    def send_headers(self): 
    329         hkeys = [key.lower() for (key,value) in self.outheaders] 
    330          
     344        hkeys = [key.lower() for (key, value) in self.outheaders] 
    331345        status = int(self.status[:3]) 
    332         if (self.response_protocol == 'HTTP/1.1' 
    333             and (# Request Entity Too Large. Close conn to avoid garbage. 
    334                 status == 413 
    335                 # No Content-Length. Close conn to determine transfer-length. 
    336                 or ("content-length" not in hkeys and 
    337                     # "All 1xx (informational), 204 (no content), 
    338                     # and 304 (not modified) responses MUST NOT 
    339                     # include a message-body." 
    340                     status >= 200 and status not in (204, 304)))): 
    341             if "connection" not in hkeys: 
    342                 self.outheaders.append(("Connection", "close")) 
    343             self.close_connection = True 
     346         
     347        if self.response_protocol == 'HTTP/1.1': 
     348            if status == 413: 
     349                # Request Entity Too Large. Close conn to avoid garbage. 
     350                self.close_connection = True 
     351            elif "content-length" not in hkeys: 
     352                if status in (200, 203, 206): 
     353                    # Use the chunked transfer-coding 
     354                    self.chunked_write = True 
     355                    self.outheaders.append(("Transfer-Encoding", "chunked")) 
     356                # "All 1xx (informational), 204 (no content), 
     357                # and 304 (not modified) responses MUST NOT 
     358                # include a message-body." 
     359                elif status >= 200 and status not in (204, 205, 304): 
     360                    # Close conn to determine transfer-length. 
     361                    self.close_connection = True 
     362         
     363        if self.close_connection and "connection" not in hkeys: 
     364            self.outheaders.append(("Connection", "close")) 
    344365         
    345366        if "date" not in hkeys: 

Hosted by WebFaction

Log in as guest/cpguest to create tickets