Faire un transfert de fichier SFTP avec Twisted Conch implique deux phases distinctes (bien, elles sont distinctes si vous plissez les yeux). Fondamentalement, vous devez d'abord établir une connexion avec un canal ouvert avec un sous-système sftp fonctionnant dessus. Ouf. Vous pouvez ensuite utiliser les méthodes d'une instance FileTransferClient connectée à ce canal pour effectuer les opérations SFTP que vous souhaitez effectuer.
L'essentiel de l'obtention d'une connexion SSH peut être pris en charge par les API fournies par les modules du package twisted.conch.client. Voici une fonction qui enveloppe la légère bizarreries de twisted.conch.client.default.connect
dans une interface un peu moins surprenant:
from twisted.internet.defer import Deferred
from twisted.conch.scripts.cftp import ClientOptions
from twisted.conch.client.connect import connect
from twisted.conch.client.default import SSHUserAuthClient, verifyHostKey
def sftp(user, host, port):
options = ClientOptions()
options['host'] = host
options['port'] = port
conn = SFTPConnection()
conn._sftp = Deferred()
auth = SSHUserAuthClient(user, options, conn)
connect(host, port, options, verifyHostKey, auth)
return conn._sftp
Cette fonction prend un nom d'utilisateur, le nom d'hôte (ou adresse IP) et le numéro de port et établit une connexion SSH authentifiées à la serveur à cette adresse en utilisant le compte associé au nom d'utilisateur donné.
En fait, il en fait un peu plus que ça, parce que la configuration SFTP est un peu mélangée ici. Pour l'instant cependant, ignorez SFTPConnection
et que _sftp
différé.
ClientOptions
est fondamentalement juste un dictionnaire de fantaisie que connect
veut être en mesure de voir à quoi il se connecte afin qu'il puisse vérifier la clé de l'hôte.
SSHUserAuthClient
est l'objet qui définit comment l'authentification se produira. Cette classe sait comment essayer les choses habituelles comme regarder ~/.ssh
et parler à un agent local SSH. Si vous voulez changer la façon dont l'authentification est faite, c'est l'objet à jouer avec. Vous pouvez sous-classer SSHUserAuthClient
et remplacer ses méthodes getPassword
, getPublicKey
, getPrivateKey
et/ou signData
, ou vous pouvez écrire votre propre classe complètement différente qui a n'importe quelle autre logique d'authentification que vous voulez. Jetez un coup d'œil à l'implémentation pour voir les méthodes utilisées par l'implémentation du protocole SSH pour obtenir l'authentification. Par conséquent, cette fonction établira une connexion SSH et l'authentifiera. Après cela, l'instance SFTPConnection
entre en jeu. Notez comment SSHUserAuthClient
prend l'instance SFTPConnection
en tant qu'argument.Une fois l'authentification réussie, il transmet le contrôle de la connexion à cette instance. En particulier, cette instance a été appelée serviceStarted
. Voici la mise en œuvre intégrale de la classe SFTPConnection
:
class SFTPConnection(SSHConnection):
def serviceStarted(self):
self.openChannel(SFTPSession())
très simple: tout ce qu'il fait est ouvert un nouveau canal. L'instance SFTPSession
qu'il passe à interagit avec ce nouveau canal. Voici comment je définissais SFTPSession
:
class SFTPSession(SSHChannel):
name = 'session'
def channelOpen(self, whatever):
d = self.conn.sendRequest(
self, 'subsystem', NS('sftp'), wantReply=True)
d.addCallbacks(self._cbSFTP)
def _cbSFTP(self, result):
client = FileTransferClient()
client.makeConnection(self)
self.dataReceived = client.dataReceived
self.conn._sftp.callback(client)
Comme avec SFTPConnection
, cette classe a une méthode qui est appelée lorsque la connexion est prête. Dans ce cas, il est appelé lorsque le canal est ouvert avec succès, et la méthode est channelOpen
. Enfin, les exigences pour le lancement du sous-système SFTP sont en place. Ainsi, channelOpen
envoie une requête sur le canal pour lancer ce sous-système. Il demande une réponse afin qu'il puisse dire quand cela a réussi (ou échoué). Il ajoute un rappel au Deferred
il obtient de se connecter un FileTransferClient
à lui-même.
L'instance FileTransferClient
va réellement formater et analyser les octets qui se déplacent sur ce canal de la connexion. En d'autres termes, il s'agit d'une implémentation de juste le protocole SFTP. Il s'exécute sur le protocole SSH, que les autres objets créés par cet exemple prennent en charge. Mais en ce qui le concerne, il reçoit des octets dans sa méthode dataReceived
, les analyse et distribue des données aux callbacks, et propose des méthodes qui acceptent les objets Python structurés, les formate comme octets de droite et les écrit dans son transport.
Cependant, rien de tout cela n'est directement important pour l'utiliser. Toutefois, avant de donner un exemple d'exécution d'actions SFTP, couvrons cet attribut _sftp
. C'est mon approche brute pour rendre cette instance FileTransferClient
nouvellement connectée disponible à un autre code qui saura réellement quoi faire avec. Séparer le code d'installation SFTP du code qui utilise réellement la connexion SFTP facilite la réutilisation de l'ancien en modifiant le dernier.
Ainsi, le Deferred
que j'ai défini dans sftp
est déclenché par le FileTransferClient
connecté dans _cbSFTP
. Et l'appelant de sftp
a qui Deferred
les retournés, de sorte que le code peut faire des choses comme ceci:
def transfer(client):
d = client.makeDirectory('foobarbaz', {})
def cbDir(ignored):
print 'Made directory'
d.addCallback(cbDir)
return d
def main():
...
d = sftp(user, host, port)
d.addCallback(transfer)
donc d'abord sftp
met en place la connexion entière, tout le chemin à la connexion d'une FileTransferClient
instance locale jusqu'à un octet flux qui a un sous-système SFTP du serveur SSH à l'autre extrémité, puis transfer
prend cette instance et l'utilise pour créer un répertoire, en utilisant l'une des méthodes de FileTransferClient
pour effectuer une opération SFTP.
Voici une liste de code que vous devriez être en mesure de courir et de voir un répertoire créé sur un serveur SFTP:
from sys import stdout
from twisted.python.log import startLogging, err
from twisted.internet import reactor
from twisted.internet.defer import Deferred
from twisted.conch.ssh.common import NS
from twisted.conch.scripts.cftp import ClientOptions
from twisted.conch.ssh.filetransfer import FileTransferClient
from twisted.conch.client.connect import connect
from twisted.conch.client.default import SSHUserAuthClient, verifyHostKey
from twisted.conch.ssh.connection import SSHConnection
from twisted.conch.ssh.channel import SSHChannel
class SFTPSession(SSHChannel):
name = 'session'
def channelOpen(self, whatever):
d = self.conn.sendRequest(
self, 'subsystem', NS('sftp'), wantReply=True)
d.addCallbacks(self._cbSFTP)
def _cbSFTP(self, result):
client = FileTransferClient()
client.makeConnection(self)
self.dataReceived = client.dataReceived
self.conn._sftp.callback(client)
class SFTPConnection(SSHConnection):
def serviceStarted(self):
self.openChannel(SFTPSession())
def sftp(user, host, port):
options = ClientOptions()
options['host'] = host
options['port'] = port
conn = SFTPConnection()
conn._sftp = Deferred()
auth = SSHUserAuthClient(user, options, conn)
connect(host, port, options, verifyHostKey, auth)
return conn._sftp
def transfer(client):
d = client.makeDirectory('foobarbaz', {})
def cbDir(ignored):
print 'Made directory'
d.addCallback(cbDir)
return d
def main():
startLogging(stdout)
user = 'exarkun'
host = 'localhost'
port = 22
d = sftp(user, host, port)
d.addCallback(transfer)
d.addErrback(err, "Problem with SFTP transfer")
d.addCallback(lambda ignored: reactor.stop())
reactor.run()
if __name__ == '__main__':
main()
makeDirectory
est une opération assez simple. La méthode makeDirectory
renvoie un Deferred
qui se déclenche lorsque le répertoire a été créé (ou en cas d'erreur). Le transfert d'un fichier est un peu plus complexe, car vous devez fournir les données à envoyer ou définir comment les données reçues seront interprétées si vous téléchargez au lieu de les télécharger.
Si vous lisez les docstrings pour les méthodes de FileTransferClient
, cependant, vous devriez voir comment utiliser ses autres fonctionnalités - pour le transfert de fichiers réel, openFile
est principalement d'intérêt. Il vous donne un Deferred
qui se déclenche avec un fournisseur ISFTPFile. Cet objet a des méthodes pour lire et écrire le contenu du fichier.
Pouvez-vous expliquer comment vous êtes bloqué plus spécifiquement? Comme votre question est maintenant, la seule façon que je peux penser pour y répondre est d'écrire un tutoriel complet Conch/SFTP, qui est probablement plus de travail que 15 points sur SO vaut (au moins pour le moment). ;) Mais une question plus spécifique pourrait avoir une réponse plus simple. –
@ Jean-Paul En ce moment je pense que j'ai besoin de sous-classer t.c.s.f.FileTransferClient. Je pense aussi que j'ai besoin d'ouvrir une connexion SSH similaire à l'exemple que j'ai lié ci-dessus. Je suis coincé avec la façon de sous-classer correctement t.c.s.f.FileTransferClient et comment déplacer réellement les fichiers. Un tutoriel complet n'est pas nécessaire car je suis intéressé par l'apprentissage tordu (c'était mon premier petit projet) mais un croquis des méthodes et des classes que je devrais utiliser ou lire ou même un exemple simple dans les docs (j'ai trouvé cftp.py à difficile à lire) serait très apprécié. – rymurr