10 votes

Pourquoi Twisted pense-t-il que j'appelle request.finish() deux fois alors que ce n'est pas le cas ?

C'est un problème ennuyeux que je rencontre avec Twisted.web. En gros, j'ai une classe qui hérite de twisted.web.resource.Resource et ajoute quelques éléments par défaut aux modèles Mako :

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 ""

Ensuite, et j'ai réduit le problème à cette petite classe (que j'ai testée), j'écris une ressource qui hérite de 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()

J'aimerais noter que, dans tous les autres cas. request.finish() n'est pas la dernière ligne d'une fonction, alors je return immédiatement après.

Quoi qu'il en soit, j'ajoute cette classe au site à l'adresse suivante /test et quand j'y navigue, j'obtiens un bouton d'envoi. Je clique sur le bouton d'envoi, et dans la console j'obtiens :

C:\\Python26\\lib\\site-packages\\twisted\\web\\server.py:200: UserWarning: Warning! request.finish called twice.
  self.finish()

Mais je n'obtiens ce résultat que la première fois que je soumets la page. Toutes les autres fois, tout va bien. Je devrais simplement ignorer ce problème, mais il me harcèle, et je n'arrive pas à comprendre pourquoi il fait cela, et pourquoi seulement la première fois que la page est soumise. Je n'arrive pas à trouver quoi que ce soit en ligne, et même en laissant tomber les déclarations d'impression et les traces dans le fichier request.finish() Le code n'a rien révélé.

modifier

Ce matin, j'ai essayé d'ajouter un deuxième request.finish() à la ressource, et l'erreur ne s'est produite qu'une seule fois. Je suppose qu'il n'avertit qu'une seule fois de la présence d'une ressource - peut-être par exécution du programme ou par session, je ne suis pas sûr. Dans tous les cas, je l'ai changé en :

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()

et j'ai reçu deux messages, en une seule fois. Je n'ai toujours aucune idée de la raison pour laquelle je ne peux pas rediriger la requête sans qu'il dise que je l'ai terminée deux fois (parce que je ne peux pas rediriger sans request.finish() ).

11voto

Carson Myers Points 11135

Réponse courte


Il doit l'être :

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

Réponse longue


J'ai décidé d'aller fouiller dans le code source de Twisted. J'ai d'abord ajouté un traceback à la zone qui imprime l'erreur si request.finish() est appelé deux fois :

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
    #....

...
  File "C:\\Python26\\lib\\site-packages\\twisted\\web\\server.py", line 200, in render
    self.finish()
  File "C:\\Python26\\lib\\site-packages\\twisted\\web\\http.py", line 904, in finish
    traceback.print\_stack()

Je suis entré et j'ai vérifié render en twisted.web.server et j'ai trouvé ça :

    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 est le résultat du rendu d'une ressource, donc une fois que body est remplie, dans le cas de l'exemple donné dans ma question, finish a déjà été appelé sur cet objet de requête (puisque la commande self est transmis de cette méthode à la méthode de rendu de la ressource).

En regardant ce code, il devient évident qu'en retournant NOT_DONE_YET J'éviterais l'avertissement.

J'aurais également pu changer la dernière ligne de cette méthode en :

if not self.finished:
    self.finish()

mais, dans l'intérêt de ne pas modifier la bibliothèque, la réponse courte est :

après avoir appelé request.redirect() vous devez appeler request.finish() et ensuite return twisted.web.server.NOT_DONE_YET

Plus de


J'ai trouvé quelques documentation à ce sujet. Ce n'est pas lié à la redirection d'une requête, mais plutôt au rendu d'une ressource, à l'aide de la fonction request.write() . Il est dit d'appeler request.finish() et retourner ensuite NOT_DONE_YET . En regardant le code dans render() Je peux comprendre pourquoi c'est le cas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X