2010-10-19 3 views
2

J'écris un frontal simple basé sur un navigateur qui devrait être capable de lancer une tâche en arrière-plan et d'en tirer parti. Je souhaite que le navigateur reçoive une réponse indiquant si la tâche a été lancée avec succès, puis interroge pour déterminer quand elle est terminée. Cependant, la présence d'une tâche en arrière-plan semble empêcher la réponse de XMLHttpRequest d'être envoyée immédiatement, donc je ne peux pas rapporter le succès du lancement du processus. Considérez le code suivant (simplifié):Pourquoi une tâche d'arrière-plan bloque-t-elle la réponse dans SimpleHTTPServer?

import SocketServer 
import SimpleHTTPServer 
import multiprocessing 
import time 

class MyProc(multiprocessing.Process): 
    def run(self): 
     print 'Starting long process..' 
     for i in range(100): time.sleep(1) 
     print 'Done long process' 

class Page(SimpleHTTPServer.SimpleHTTPRequestHandler): 
    def do_GET(self): 
     if self.path == '/': 
      print >>self.wfile, "<html><body><a href='/run'>Run</a></body></html>" 
     if self.path == '/run': 
      self.proc = MyProc() 
      print 'Starting..' 
      self.proc.start() 
      print 'After start.' 
      print >>self.wfile, "Process started." 

httpd = SocketServer.TCPServer(('', 8000), Page) 
httpd.serve_forever() 

Quand je lance cela, et accédez à http://localhost:8000, je reçois un bouton nommé "Run". Quand je clique dessus, le terminal affiche:

Starting.. 
After start. 

Cependant, la vue du navigateur ne change pas .. En fait, le curseur tourne. Seulement quand j'appuie sur Ctrl-C dans le terminal pour interrompre le programme, le navigateur est mis à jour avec le message Process started.

Le message After start est clairement imprimé. Par conséquent, je peux supposer que do_GET est de retour après le démarrage du processus. Pourtant, le navigateur n'obtient pas de réponse avant d'avoir interrompu le processus de longue durée. Je dois conclure qu'il y a quelque chose qui bloque entre do_GET et la réponse envoyée, qui est à l'intérieur SimpleHTTPServer.

J'ai également essayé ceci avec les discussions et le sous-processus. Ouvrez mais avez rencontré des problèmes semblables. Des idées?

+0

p.s. J'utilise Python 2.6.5. N'a pas testé sur d'autres versions. – Steve

+0

Vous n'envoyez pas de réponse HTTP. Veuillez regarder ce que l'implémentation de base de 'do_GET' fait réellement avant de l'écraser sans fournir un substitut approprié. –

+0

L'ajout de 'print >> self.wfile," Content-Type: text/plain \ n "' avant "Process started" ne fait aucune différence, le résultat est le même. – Steve

Répondre

3

En plus de Steve et mes commentaires ci-dessus, voici une solution qui fonctionne pour moi.

La méthode pour déterminer une longueur de contenu est un peu moche. Si vous n'en spécifiez pas, le navigateur peut toujours afficher un curseur tournant bien que le contenu soit affiché. Fermer la self.wfile à la place pourrait également fonctionner.

from cStringIO import StringIO 

class Page(SimpleHTTPServer.SimpleHTTPRequestHandler): 
    def do_GET(self): 
     out = StringIO() 
     self.send_response(200) 
     self.send_header("Content-type", "text/html") 
     if self.path == '/': 
      out.write("<html><body><a href='/run'>Run</a></body></html>\n") 
     elif self.path == '/run': 
      self.proc = MyProc() 
      print 'Starting..' 
      self.proc.start() 
      print 'After start.' 
      out.write("<html><body><h1>Process started</h1></body></html>\n") 
     text = out.getvalue() 
     self.send_header("Content-Length", str(len(text))) 
     self.end_headers() 
     self.wfile.write(text) 
+0

Cool, ça marche, merci! Quand j'ai dit que j'ai appelé 'send_header' ci-dessus, je l'ai seulement utilisé pour Content-type. Il semble que Content-length soit important ici, ce que je suppose signifie qu'il a transféré des données mais n'a pas réellement fermé la connexion GET. Je voudrais avoir compris plus profondément pourquoi .. l'ouverture d'un processus en arrière-plan arrête en quelque sorte SimpleHTTPServer de fermer la connexion? J'ai essayé 'self.wfile.close()' dans mon exemple original mais cela n'a rien changé. – Steve

+0

@Steve: Les connexions HTTP sont souvent laissées ouvertes car il est alors possible de faire plusieurs requêtes sans se reconnecter (Pipelining). Il y a aussi un en-tête HTTP pour cela ('Connection:', 'keep-alive'). La longueur du contenu est nécessaire pour informer le navigateur lorsque toutes les données sont reçues. Dans mon exemple, Firefox pourrait rendre la page sans contenu-longueur mais a montré le curseur tournant. Tout cela n'est pas lié à votre processus d'arrière-plan. –

+0

Je vois. C'est juste que la même chose ne s'est pas produite sans le processus d'arrière-plan. En tout cas, je comprends que c'est probablement une bonne pratique d'inclure la longueur du contenu. – Steve

0

La réponse est que les fourches du module multiprocessing un process complètement différent avec son propre stdout ... Donc, votre application est en cours d'exécution comme vous l'avez écrit:

  1. Vous démarrez l'application dans votre fenêtre de terminal .
  2. Vous cliquez sur le bouton Exécuter dans votre navigateur qui fait un GET sur/run
  3. Vous voyez la sortie du processus en cours dans votre fenêtre de terminal, « A partir .. »
  4. Un nouveau processus est démarré , MyProc avec ses propres stdout et stderr.
  5. MyProc imprime sur sa sortie stdout ( ne va nulle part), 'Starting long process ..'.
  6. Au moment même où MyProc démarre, votre application imprime sur stdout, "Après le démarrage". car il était pas dit d'attendre une réponse de MyProc avant de faire donc.

Ce dont vous avez besoin est de mettre en œuvre une file d'attente qui communique entre le processus de votre application principale et le processus en fourche. Il y a quelques exemples spécifiques à multitraitement sur la façon de le faire ici:

http://www.ibm.com/developerworks/aix/library/au-multiprocessing/

Toutefois, cet article (comme la plupart des articles d'IBM) est une sorte de profond et trop compliqué ...Vous voudrez peut-être jeter un oeil à un simple exemple de la façon d'utiliser le module File d'attente « régulière » (il est à peu près identique à celui inclus dans multitraitement):

http://www.artfulcode.net/articles/multi-threading-python/

Les concepts les plus importants pour comprendre sont comment mélanger les données entre les processus en utilisant la file d'attente et comment utiliser join() pour attendre une réponse avant de continuer.

+0

Mon problème n'est pas avec la communication au multiprocessus, mon problème est de ne pas obtenir le message "Process started" dans le navigateur jusqu'à ce que je _quit_ le multiprocessus. Je devrais recevoir ce message immédiatement, car "Après le démarrage" est clairement imprimé, mais à la place il n'y a pas de réponse HTTP jusqu'à ce que l'autre processus se termine, même si je ne l'attends pas. – Steve

1

J'utilise cet extrait pour exécuter la version filaire de SimpleHTTPServer.

J'enregistrer ce fichier comme ThreadedHTTPServer.py par exemple, puis je cours comme ça:

$ python -m /path/to/ThreadedHTTPServer PORT

donc ça va être taraudés dans les discussions et maintenant séparés, vous pouvez télécharger dans paralell et naviguer correctement.

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 
from SocketServer import ThreadingMixIn 
import threading 
import SimpleHTTPServer 
import sys 

PORT = int(sys.argv[1]) 

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler 

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): 
    """Handle requests in a separate thread.""" 

if __name__ == '__main__': 
    server = ThreadedHTTPServer(('0.0.0.0', PORT), Handler) 
    print 'Starting server, use <Ctrl-C> to stop' 
    server.serve_forever() 
+0

Excellent! Cela devrait être la réponse acceptée. – ccpizza

Questions connexes