2010-11-29 50 views
1

Est-ce que quelqu'un a réussi à implémenter la commande REST dans le serveur FTP torsadé? Ma tentative actuelle:Implémentation de REST dans twisted.protocols.ftp.FTP?

from twisted.protocols import ftp 
from twisted.internet import defer 

class MyFTP(ftp.FTP): 
    def ftp_REST(self, pos): 
     try: 
      pos = int(pos) 
     except ValueError: 
      return defer.fail(CmdSyntaxError('Bad argument for REST')) 

     def all_ok(result): 
      return ftp.REQ_FILE_ACTN_PENDING_FURTHER_INFO # 350 

     return self.shell.restart(pos).addCallback(all_ok) 

class MyShell(ftp.FTPShell): 
    def __init__(self, host, auth): 
     self.position = 0 
     ... 

    def restart(self, pos): 
     self.position = pos 
     print "Restarting at %s"%pos 
     return defer.succeed(pos) 

Lorsqu'un client envoie une commande REST, il faut quelques secondes avant que je vois à la sortie du script:

Traceback (most recent call last): 
Failure: twisted.protocols.ftp.PortConnectionError: DTPFactory timeout 
Restarting at <pos> 

Qu'est-ce que je fais mal? Il me semble qu'une réponse devrait suivre immédiatement la commande REST, pourquoi le socket est-il sorti?

Mise à jour:

Après avoir activé l'enregistrement comme suggéré par Jean-Paul Calderone, il ressemble à la commande REST est même pas fait à ma classe FTP avant que les temps de connexion PAO par manque de connexion (horodatages réduit à MM: SS par souci de brièveté):

09:53 [TrafficLoggingProtocol,1,127.0.0.1] cleanupDTP 
09:53 [TrafficLoggingProtocol,1,127.0.0.1] <<class 'twisted.internet.tcp.Port'> of twisted.protocols.ftp.DTPFactory on 37298> 
09:53 [TrafficLoggingProtocol,1,127.0.0.1] dtpFactory.stopFactory 
09:53 [-] (Port 37298 Closed) 
09:53 [-] Stopping factory <twisted.protocols.ftp.DTPFactory instance at 0x8a792ec> 
09:53 [-] dtpFactory.stopFactory 
10:31 [-] timed out waiting for DTP connection 
10:31 [-] Unexpected FTP error 
10:31 [-] Unhandled Error 
     Traceback (most recent call last): 
     Failure: twisted.protocols.ftp.PortConnectionError: DTPFactory timeout 

10:31 [TrafficLoggingProtocol,2,127.0.0.1] Restarting at 1024 

la commande retourne ftp_PASVDTPFactory.deferred, qui est décrit comme un « report [qui] se déclenche lorsque l'instance est connecté ». Les commandes RETR passent bien (ftp.FTP serait sans valeur). Cela me porte à croire qu'il y a une sorte d'opération de blocage ici qui ne laissera rien d'autre se produire jusqu'à ce que la connexion DTP soit établie; alors et seulement alors pouvons-nous accepter d'autres commandes. Malheureusement, il semble que certains (tous?) Clients (en particulier, je teste avec FileZilla) envoient la commande REST avant de se connecter en essayant de reprendre un téléchargement.

Répondre

1

Après beaucoup de creuser dans la source et de jongler avec des idées, c'est la solution je me suis installé sur:

class MyFTP(ftp.FTP): 
    dtpTimeout = 30 

    def ftp_PASV(self): 
    # FTP.lineReceived calls pauseProducing(), and doesn't allow 
    # resuming until the Deferred that the called function returns 
    # is called or errored. If the client sends a REST command 
    # after PASV, they will not connect to our DTP connection 
    # (and fire our Deferred) until they receive a response. 
    # Therefore, we will turn on producing again before returning 
    # our DTP's deferred response, allowing the REST to come 
    # through, our response to the REST to go out, the client to 
    # connect, and everyone to be happy. 
    resumer = reactor.callLater(0.25, self.resumeProducing) 
    def cancel_resume(_): 
     if not resumer.called: 
     resumer.cancel() 
     return _ 
    return ftp.FTP.ftp_PASV(self).addBoth(cancel_resume) 
    def ftp_REST(self, pos): 
    # Of course, allowing a REST command to come in does us no 
    # good if we can't handle it. 
    try: 
     pos = int(pos) 
    except ValueError: 
     return defer.fail(CmdSyntaxError('Bad argument for REST')) 

    def all_ok(result): 
     return ftp.REQ_FILE_ACTN_PENDING_FURTHER_INFO 

    return self.shell.restart(pos).addCallback(all_ok) 

class MyFTPShell(ftp.FTPShell): 
    def __init__(self, host, auth): 
    self.position = 0 

    def restart(self, pos): 
    self.position = pos 
    return defer.succeed(pos) 

L'approche callLater peut être squameuse parfois, mais il fonctionne la plupart du temps. Utilisez à vos risques et périls, évidemment.

+0

Je pense que nous aimerions probablement résoudre ce problème dans Twisted, aussi. Avez-vous envie de décrire le problème dans un nouveau ticket sur http://twistedmatrix.com/? –

+0

@ Jean-Paul Claderone, bien sûr: http://twistedmatrix.com/trac/ticket/4819 – eternicode

+0

Merci beaucoup. :) –

2

Vérifiez que le client se comporte comme prévu. La capture de tout le trafic pertinent avec tcpdump ou wireshark est un bon moyen de le faire, bien que vous puissiez également activer la connexion dans votre serveur FTP Twisted de plusieurs façons (par exemple, en utilisant le wrapper d'usine twisted.protocols.policies.TrafficLoggingFactory). À partir de l'erreur de délai d'attente suivie par le message de journal «Redémarrage ...», je deviner que le client envoie un «premier» RETR, puis un REST. Le RETR expire car le client n'essaie pas de se connecter au canal de données avant d'avoir reçu une réponse au REST, et le serveur Twisted ne traite même pas le REST tant que le client ne s'est pas connecté au canal de données (et téléchargements le fichier entier). Cela peut nécessiter de changer la façon dont les commandes des clients sont traitées, de sorte qu'un REST qui suit un RETR puisse être interprété correctement (ou peut-être que le client FTP que vous utilisez est bogué, d'après la documentation du protocole, RETR est censé suivez REST, pas l'inverse). Ceci est juste une supposition, cependant, et vous devriez regarder la capture du trafic pour le confirmer ou le rejeter.

+0

Après l'activation de la journalisation, il semble que c'est la connexion DTP (à partir de la commande PSV) qui bloque. Le client envoie PSV, puis REST (et puis RETR, je pense, bien qu'il ne soit jamais aussi loin). On dirait que le problème est que le client ne se connecte pas au DTP avant d'envoyer REST (et attend une réponse), nous envoyant tous les deux dans un cercle d'attente. Ils attendent une réponse à REST et j'attends la connexion DTP.Je suppose que c'est pourquoi REST n'est pas implémenté par défaut. Figures – eternicode

+1

Il y a quelque chose qui bloque le traitement d'autres commandes - bien qu'il ne bloque pas dans le sens où il empêche tout autre code de s'exécuter dans le thread du réacteur. 'FTP.lineReceived' commence par un appel à' pauseProducing'. Ceci fait que le réacteur arrête de lire depuis sa connexion jusqu'à ce que "resumeProducing" soit appelé. Et 'FTP' appelle seulement' resumeProducing' après avoir décidé qu'il a entièrement traité la ligne. Dans le cas de PASV, "entièrement traité" signifie qu'un client s'est * connecté * au port DTP. Donc vous avez raison, car le protocole FTP est actuellement implémenté, REST ne peut jamais fonctionner. –