10

这是我在使用 Twisted.web 时遇到的一个烦人的问题。基本上,我有一个继承自twisted.web.resource.ResourceMako 模板并向其添加一些默认内容的类:

from twisted.web.resource import Resource
from mako.lookup import TemplateLookup
from project.session import SessionData
from project.security import make_nonce


class Page(Resource):

    template = ""

    def display(self, request, **kwargs):
        session = SessionData(request.getSession())

        if self.template:
            templates = TemplateLookup(directories=['templates'])
            template = templates.get_template(self.template)
            return template.render(user=session.user,
                                   info=session.info,
                                   current_path=request.path,
                                   nonce=make_nonce(session),
                                   **kwargs)
        else:
            return ""

然后,我将问题缩小到这个小类(我测试过),我编写了一个继承自的资源Page

class Test(pages.Page):
    def render_GET(self, request):
        return "<form method='post'><input type='submit'></form>"
    def render_POST(self, request):
        request.redirect("/test")
        request.finish()

我想指出,在所有其他情况下,如果request.finish()不是函数中的最后一行,那么我return会立即执行它。

无论如何,我将这个类添加到网站,/test当我在那里导航时,我得到一个提交按钮。我单击提交按钮,在控制台中我得到:

C:\Python26\lib\site-packages\twisted\web\server.py:200:用户警告:警告!request.finish 调用了两次。
  self.finish()

但是,我只有在第一次提交页面时才得到这个。每隔一段时间就好了。我会忽略这一点,但它一直在唠叨我,我一生都无法弄清楚它为什么会这样做,以及为什么只有第一次提交页面。我似乎在网上找不到任何东西,甚至在request.finish()代码中删除打印语句和回溯也没有透露任何内容。

编辑

今天早上我尝试在资源中添加第二request.finish()行,但它仍然只给了我一次错误。我想它只会在资源中警告它一次——也许每次运行程序,或者每次会话,我不确定。无论如何,我将其更改为:

class Test(pages.Page):
    def render_GET(self, request):
        return "<form method='post'><input type='submit'></form>"
    def render_POST(self, request):
        request.redirect("/test")
        request.finish()
        request.finish()

刚收到两条消息,一次。我仍然不知道为什么我不能在没有它说我完成了两次的情况下重定向请求(因为我不能没有重定向request.finish())。

4

1 回答 1

11

简答


它一定要是:

request.redirect("/test")
request.finish()
return twisted.web.server.NOT_DONE_YET

长答案


我决定去筛选一些 Twisted 源代码。request.finish()如果被调用两次,我首先添加了一个回溯到打印错误的区域:

def finish(self):
    import traceback #here
    """
    Indicate that all response data has been written to this L{Request}.
    """
    if self._disconnected:
        raise RuntimeError(
            "Request.finish called on a request after its connection was lost; "
            "use Request.notifyFinish to keep track of this.")
    if self.finished:
        warnings.warn("Warning! request.finish called twice.", stacklevel=2)
        traceback.print_stack() #here
        return
    #....
...
  文件“C:\Python26\lib\site-packages\twisted\web\server.py”,第 200 行,在渲染中
    self.finish()
  文件“C:\Python26\lib\site-packages\twisted\web\http.py”,第 904 行,完成
    追溯.print_stack()

我进去检查了一下rendertwisted.web.server发现这个:

    if body == NOT_DONE_YET:
        return
    if type(body) is not types.StringType:
        body = resource.ErrorPage(
            http.INTERNAL_SERVER_ERROR,
            "Request did not return a string",
            "Request: " + html.PRE(reflect.safe_repr(self)) + "<br />" +
            "Resource: " + html.PRE(reflect.safe_repr(resrc)) + "<br />" +
            "Value: " + html.PRE(reflect.safe_repr(body))).render(self)

    if self.method == "HEAD":
        if len(body) > 0:
            # This is a Bad Thing (RFC 2616, 9.4)
            log.msg("Warning: HEAD request %s for resource %s is"
                    " returning a message body."
                    "  I think I'll eat it."
                    % (self, resrc))
            self.setHeader('content-length', str(len(body)))
        self.write('')
    else:
        self.setHeader('content-length', str(len(body)))
        self.write(body)
    self.finish()

body是渲染资源的结果,因此body在我的问题中给出的示例情况下,一旦填充,finish已经在此请求对象上调用(因为从此self方法传递到资源的渲染方法)。

通过查看这段代码,很明显通过返回NOT_DONE_YET我可以避免警告。

我也可以将该方法的最后一行更改为:

if not self.finished:
    self.finish()

但是,为了不修改库,简短的回答是:

打电话后request.redirect()你必须打电话request.finish(),然后return twisted.web.server.NOT_DONE_YET

更多的


我找到了一些关于此的文档。它与重定向请求无关,而是使用request.write(). 它说调用request.finish()然后返回NOT_DONE_YET。通过查看中的代码,render()我可以明白为什么会这样。

于 2010-07-16T22:19:04.363 回答