← Back to team overview

divmod-dev team mailing list archive

[Bug 892794] [NEW] Nevow should handle interrupted HTTP responses

 

Public bug reported:

(This is copied from the mailing list. I enclose a patch, which includes
a unit test.)

J.P's excellent article http://jcalderone.livejournal.com/50890.html
explains how to avoid calling Request.finish() on a request after its
connection was lost, because that now raises an exception. The code in
the article is able to avoid calling finish() because it handles the
rendering itself. The call to finish() is right there in _delayedRender.
But I'm using Nevow (because it's awesome) and all that stuff is done
behind the scenes, where I can't get to it.

My application has a form, which uses the Post/Redirect/Get pattern
(http://en.wikipedia.org/wiki/Post/Redirect/Get) to avoid
double-posting. Because processing is not instant, the browser receives
the HTTP header telling it to redirect before the processing has
finished. When the processing finishes, the Deferred embedded in the web
page fires with a value, Stan finishes flattening the page, and Nevow
calls Request.finish(). By then the browser has closed the connection
and sent in the GET request. So I get an error every single time.
Any page that uses Deferreds and redirects will do as well.

This is a completely self-contained example, to be run with twistd:


from nevow import tags, inevow, loaders, appserver
from nevow.rend import Page
from nevow.url import URL
from twisted.internet.task import deferLater
from twisted.internet import reactor
from twisted.application import service
from twisted.application.internet import TCPServer
from twisted.web import http

class Redirector(Page):

    count = 0
    message = 0

    def _increment(self):
        self.count += 1
        return self.count

    def slow_operation(self):
        return deferLater(reactor, 3, self._increment)

    def beforeRender(self, ctx):
        req = inevow.IRequest(ctx)
        if req.method == 'POST':
            oldurl = URL.fromContext(ctx)
            newurl = oldurl.remove('cmd')
            req.setResponseCode(http.SEE_OTHER)
            req.setHeader("Location", newurl)
            self.message = self.slow_operation()

    def render_message(self, ctx, data):
        return self.message

    docFactory = loaders.stan(
        tags.html[
            tags.head[tags.title['Slow page']],
            tags.body[
                tags.h1['Problem with interrupted connections'],
                tags.form(method='post', action='')[
                    tags.button(type='submit', name='cmd',
                    value='click')[
                        'Click'
                        ]
                    ],
                tags.p(render=tags.directive('message'))
                ]
            ]
        )

application = service.Application('Demo')

site = appserver.NevowSite(Redirector())
webservice = TCPServer(8123, site)
webservice.setName('WUI')
webservice.setServiceParent(application)

** Affects: nevow
     Importance: Undecided
         Status: New

-- 
You received this bug notification because you are a member of Divmod-
dev, which is the registrant for nevow.
https://bugs.launchpad.net/bugs/892794

Title:
  Nevow should handle interrupted HTTP responses

Status in Divmod Nevow:
  New

Bug description:
  (This is copied from the mailing list. I enclose a patch, which
  includes a unit test.)

  J.P's excellent article http://jcalderone.livejournal.com/50890.html
  explains how to avoid calling Request.finish() on a request after its
  connection was lost, because that now raises an exception. The code in
  the article is able to avoid calling finish() because it handles the
  rendering itself. The call to finish() is right there in _delayedRender.
  But I'm using Nevow (because it's awesome) and all that stuff is done
  behind the scenes, where I can't get to it.

  My application has a form, which uses the Post/Redirect/Get pattern
  (http://en.wikipedia.org/wiki/Post/Redirect/Get) to avoid
  double-posting. Because processing is not instant, the browser receives
  the HTTP header telling it to redirect before the processing has
  finished. When the processing finishes, the Deferred embedded in the web
  page fires with a value, Stan finishes flattening the page, and Nevow
  calls Request.finish(). By then the browser has closed the connection
  and sent in the GET request. So I get an error every single time.
  Any page that uses Deferreds and redirects will do as well.

  This is a completely self-contained example, to be run with twistd:

  
  from nevow import tags, inevow, loaders, appserver
  from nevow.rend import Page
  from nevow.url import URL
  from twisted.internet.task import deferLater
  from twisted.internet import reactor
  from twisted.application import service
  from twisted.application.internet import TCPServer
  from twisted.web import http

  class Redirector(Page):

      count = 0
      message = 0

      def _increment(self):
          self.count += 1
          return self.count

      def slow_operation(self):
          return deferLater(reactor, 3, self._increment)

      def beforeRender(self, ctx):
          req = inevow.IRequest(ctx)
          if req.method == 'POST':
              oldurl = URL.fromContext(ctx)
              newurl = oldurl.remove('cmd')
              req.setResponseCode(http.SEE_OTHER)
              req.setHeader("Location", newurl)
              self.message = self.slow_operation()

      def render_message(self, ctx, data):
          return self.message

      docFactory = loaders.stan(
          tags.html[
              tags.head[tags.title['Slow page']],
              tags.body[
                  tags.h1['Problem with interrupted connections'],
                  tags.form(method='post', action='')[
                      tags.button(type='submit', name='cmd',
                      value='click')[
                          'Click'
                          ]
                      ],
                  tags.p(render=tags.directive('message'))
                  ]
              ]
          )

  application = service.Application('Demo')

  site = appserver.NevowSite(Redirector())
  webservice = TCPServer(8123, site)
  webservice.setName('WUI')
  webservice.setServiceParent(application)

To manage notifications about this bug go to:
https://bugs.launchpad.net/nevow/+bug/892794/+subscriptions


Follow ups

References