web-dev-qa-db-fra.com

Flask pipe cassée avec requêtes

Je voudrais envoyer une demande locale REST dans une application flask, comme ceci:

from flask import Flask, url_for, request
import requests

app = Flask(__name__)

@app.route("/<name>/hi", methods=["POST"])
def hi_person(name):
    form = {"name": name}
    return requests.post(url_for("hi", _external=True), data=form)

@app.route("/hi", methods=["POST"])
def hi():
    return 'Hi, %s!' % request.form["name"]

Envoi curl -X POST http://localhost:5000/john/hi provoque le gel complet de l'application flask. Lorsque j'envoie un signal d'arrêt, j'obtiens une erreur de canal cassé. Existe-t-il un moyen d'empêcher flask de gel ici?

49
jfocht

Exécutez votre application flask sous un serveur WSGI approprié capable de gérer les requêtes simultanées (peut-être gunicorn ou WSGI ) et cela fonctionnera. en développement, activez les threads dans le serveur fourni par Flask avec:

app.run(threaded=True)

mais notez que le serveur Flask n'est pas recommandé pour une utilisation en production. À partir de Flask 1.0, threaded est activé par défaut, et vous ' Je veux vraiment utiliser la commande flask sur la ligne de commande pour exécuter votre application.

Ce qui se passe, c'est qu'en utilisant les requêtes, vous effectuez une seconde requête vers votre flask app, mais comme elle est toujours occupée) en traitant la première, il ne répondra pas à cette deuxième demande tant qu'il n'aura pas terminé avec cette première demande.

Incidemment, sous Python 3, l'implémentation du serveur de sockets gère la déconnexion plus gracieusement et continue de servir plutôt que de planter.

106
Martijn Pieters

Il y a plusieurs choses en jeu ici, et j'essaierai de les aborder une par une.

Tout d'abord, vous utilisez probablement le serveur de développement de jouets. Ce serveur a de nombreuses limitations; principalement parmi ces limitations, il ne peut traiter qu'une seule demande à la fois. Lorsque vous créez une deuxième demande lors de votre première demande, vous verrouillez votre application: la fonction requests.post() attend Flask pour répondre, mais Flask lui-même attend le retour de post()! La solution à ce problème est d'exécuter votre application WSGI dans un environnement multithread ou multiprocessus. Je préfère http://twistedmatrix.com/trac/wiki/TwistedWeb pour cela, mais il existe plusieurs autres options.

Avec cela à l'écart ... Ceci est un contre-modèle. Vous ne voulez certainement pas invoquer tous les frais généraux d'une demande HTTP juste pour partager des fonctionnalités entre deux vues. La bonne chose à faire est de refactoriser pour avoir une fonction distincte qui fait ce travail partagé. Je ne peux pas vraiment refactoriser votre exemple particulier, car ce que vous avez est très simple et ne mérite même pas vraiment deux vues. Que vouliez-vous construire exactement?

Edit: Un commentaire demande si le mode multithread sur le serveur jouet stdlib serait suffisant pour empêcher le blocage de se produire. Je vais dire "peut-être". Oui, s'il n'y a pas de dépendances empêchant les deux threads de progresser et que les deux threads progressent suffisamment pour terminer leurs tâches de mise en réseau, les requêtes se termineront correctement. Cependant, déterminer si deux threads se verrouillent mutuellement est indécidable (preuve omise comme obtuse) et je ne suis pas prêt à dire avec certitude que le serveur stdlib peut le faire correctement.

19
Corbin

Le bogue à l'origine de l'accident a été corrigé dans la version 0.12 , publié le 21 décembre 2016. Ouais! Il s'agit d'un correctif important que beaucoup attendaient.

Depuis le Flask changelog:

  • Annule un changement de comportement qui a provoqué le crash du serveur de développement au lieu de renvoyer une erreur de serveur interne (pull request # 2006).
5
arotman

J'ai eu le même problème avec la méthode post, en général ma méthode post ne faisait rien, c'est pourquoi ces problèmes sont survenus

return _socket.socket.send(self._sock, data, flags) urllib3.exceptions.ProtocolError:
('Connection aborted.', BrokenPipeError(32, 'Broken pipe'))

if request.method == 'POST':
    print(len(request.data))
return 'dummy'

ce print a fait l'affaire

0
Taras Vaskiv