Ticket #3711: deferred_render-3711-landreville.patch

File deferred_render-3711-landreville.patch, 7.9 KB (added by Landreville, 4 years ago)
  • twisted/web/server.py

    === modified file 'twisted/web/server.py'
     
    126126        except: 
    127127            self.processingFailed(failure.Failure()) 
    128128 
    129  
    130129    def render(self, resrc): 
    131         try: 
    132             body = resrc.render(self) 
    133         except UnsupportedMethod, e: 
    134             allowedMethods = e.allowedMethods 
    135             if (self.method == "HEAD") and ("GET" in allowedMethods): 
    136                 # We must support HEAD (RFC 2616, 5.1.1).  If the 
    137                 # resource doesn't, fake it by giving the resource 
    138                 # a 'GET' request and then return only the headers, 
    139                 # not the body. 
    140                 log.msg("Using GET to fake a HEAD request for %s" % 
    141                         (resrc,)) 
    142                 self.method = "GET" 
    143                 body = resrc.render(self) 
    144  
    145                 if body is NOT_DONE_YET: 
    146                     log.msg("Tried to fake a HEAD request for %s, but " 
    147                             "it got away from me." % resrc) 
    148                     # Oh well, I guess we won't include the content length. 
    149                 else: 
    150                     self.setHeader('content-length', str(len(body))) 
    151  
    152                 self.write('') 
    153                 self.finish() 
    154                 return 
    155  
    156             if self.method in (supportedMethods): 
    157                 # We MUST include an Allow header 
    158                 # (RFC 2616, 10.4.6 and 14.7) 
    159                 self.setHeader('Allow', allowedMethods) 
    160                 s = ('''Your browser approached me (at %(URI)s) with''' 
    161                      ''' the method "%(method)s".  I only allow''' 
    162                      ''' the method%(plural)s %(allowed)s here.''' % { 
    163                     'URI': self.uri, 
    164                     'method': self.method, 
    165                     'plural': ((len(allowedMethods) > 1) and 's') or '', 
    166                     'allowed': string.join(allowedMethods, ', ') 
    167                     }) 
    168                 epage = resource.ErrorPage(http.NOT_ALLOWED, 
    169                                            "Method Not Allowed", s) 
    170                 body = epage.render(self) 
    171             else: 
    172                 epage = resource.ErrorPage(http.NOT_IMPLEMENTED, "Huh?", 
    173                                            "I don't know how to treat a" 
    174                                            " %s request." % (self.method,)) 
    175                 body = epage.render(self) 
    176         # end except UnsupportedMethod 
    177  
     130        """ Render a request. 
     131             
     132            This is called on the leaf resource for 
     133            a request. Render must return either a string  
     134            or a Deferred containing a string as its result. 
     135            This string will be sent to the browser as the HTML 
     136            for the request.  
     137            Render may also return NOT_DONE_YET; if NOT_DONE_YET 
     138            is returned then the resource's render method must 
     139            make any request.write calls and then request.finish. 
     140            Usually these are done in a deffered callback. This  
     141            behaviour will be deprecated in favour of returning 
     142            a Deferred. 
     143        """ 
     144        body = defer.maybeDeferred(resrc.render, self) 
     145        body.addCallbacks(self._cbRender, self._ebRender,  
     146                          callbackArgs=(resrc,), errbackArgs=(resrc,)) 
     147        return 
     148     
     149    def _cbRender(self, body, resrc): 
     150        """ Callback for render; writes the data.""" 
    178151        if body == NOT_DONE_YET: 
    179152            return 
    180         if type(body) is not types.StringType: 
     153        if not isinstance(body, str): 
    181154            body = resource.ErrorPage( 
    182155                http.INTERNAL_SERVER_ERROR, 
    183156                "Request did not return a string", 
     
    198171            self.setHeader('content-length', str(len(body))) 
    199172            self.write(body) 
    200173        self.finish() 
     174     
     175    def _ebRender(self, fail, resrc): 
     176        """ Errback for render to handle UnsupportedMethod""" 
     177        fail.trap(UnsupportedMethod) 
     178        allowedMethods = fail.value.allowedMethods 
     179        if (self.method == "HEAD") and ("GET" in allowedMethods):  
     180            log.msg("Using GET to fake a HEAD request for %s" % (resrc,)) 
     181            self.method = "GET" 
     182             
     183            body = defer.maybeDeferred(resrc.render, self) 
     184            body.addCallbacks(self._cbRenderEb, self._ebRenderEb, callbackArgs=(resrc,), errbackArgs=(resrc,)) 
     185            return body 
     186             
     187        if self.method in (supportedMethods): 
     188            # We MUST include an Allow header 
     189            # (RFC 2616, 10.4.6 and 14.7) 
     190            self.setHeader('Allow', allowedMethods) 
     191            s = ('''Your browser approached me (at %(URI)s) with''' 
     192                 ''' the method "%(method)s".  I only allow''' 
     193                 ''' the method%(plural)s %(allowed)s here.''' % { 
     194                'URI': self.uri, 
     195                'method': self.method, 
     196                'plural': ((len(allowedMethods) > 1) and 's') or '', 
     197                'allowed': string.join(allowedMethods, ', ') 
     198                }) 
     199            epage = resource.ErrorPage(http.NOT_ALLOWED, 
     200                                       "Method Not Allowed", s) 
     201             
     202            body = defer.maybeDeferred(epage.render, self) 
     203            body.addCallback(self._cbRenderEb, resrc) 
     204            return body 
     205        else: 
     206            epage = resource.ErrorPage(http.NOT_IMPLEMENTED, "Huh?", 
     207                                       "I don't know how to treat a" 
     208                                       " %s request." % (self.method,)) 
     209         
     210        body = defer.maybeDeferred(epage.render,self) 
     211        body.addCallback(self._cbRender, resrc) 
     212        return body 
     213     
     214    def _cbRenderEb(self, body, resrc): 
     215        """ Callback for render's errback; tries to write a fake HEAD request 
     216            that is created from the GET method. 
     217        """ 
     218        if body == NOT_DONE_YET: 
     219            log.msg("Tried to fake a HEAD request for %s, but " 
     220                            "it got away from me." % resrc) 
     221            # Oh well, I guess we won't include the content length. 
     222        else: 
     223            self.setHeader('content-length', str(len(body))) 
     224         
     225        self.write('') 
     226        self.finish() 
     227        return 
    201228 
    202229    def processingFailed(self, reason): 
    203230        log.err(reason) 
  • twisted/web/test/test_web.py

    === modified file 'twisted/web/test/test_web.py'
     
    861861        self.assertEqual( 
    862862            self.site.logFile.read(), 
    863863            '1.2.3.4 - - [25/Oct/2004:12:31:59 +0000] "GET /dummy HTTP/1.0" 123 - "-" "Malicious Web\\" Evil"\n') 
     864 
     865class DeferredRenderResource(resource.Resource): 
     866    """ Leaf resource that returns a Deferred from its render method. """ 
     867     
     868    isLeaf = True 
     869    def render(self, request): 
     870        d = task.deferLater(reactor, 1, lambda: 'deferred result') 
     871        d.addCallback(self._cbRender) 
     872        return d 
     873     
     874    def _cbRender(self, result): 
     875        return result 
     876 
     877 
     878class DeferredRenderTest(unittest.TestCase): 
     879    """ Test for handling a resource which returns a Deferred in its render method.""" 
     880     
     881    def testDeferredRender(self): 
     882        """ Test a request that calls a resource which will return a Deferred from its render method.""" 
     883        d = DummyChannel() 
     884        d.site.resource.putChild('deferrender', DeferredRenderResource()) 
     885        d.transport.port = 81 
     886        request = server.Request(d, 1) 
     887        request.setHost('example.com', 81) 
     888        request.gotLength(0) 
     889        request.requestReceived('GET', '/deferrender', 'HTTP/1.0') 
     890         
     891        # check that the Deferred returned by render has returned the correct string 
     892        d = task.deferLater(reactor, 3, self._testDeferredRender, request) 
     893        return d 
     894     
     895    def _testDeferredRender(self, req): 
     896        self.assertEquals(req.transport.getvalue().splitlines()[-1], 'deferred result')