Ticket #3711: deferred_render-3711-landreville.patch

File deferred_render-3711-landreville.patch, 7.9 KB (added by Landreville, 6 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')