Hacer una transferencia de archivos SFTP con Twisted Conch implica un par de fases distintas (bueno, son distintas si entrecierra los ojos). Básicamente, primero necesita configurar una conexión con un canal abierto con un subsistema sftp ejecutándose en ella. Uf. Luego puede usar los métodos de una instancia FileTransferClient conectada a ese canal para realizar cualquiera de las operaciones SFTP que desee realizar.
Las API proporcionadas por los módulos en el paquete twisted.conch.client pueden ocuparse de todo lo esencial para obtener una conexión SSH. He aquí una función que envuelve el ligero extraño de twisted.conch.client.default.connect
en una interfaz ligeramente menos sorprendente:
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
Esta función toma un nombre de usuario, nombre de host (o dirección IP), y el número de puerto y establece una conexión SSH autenticado a la servidor en esa dirección usando la cuenta asociada con el nombre de usuario dado.
En realidad, hace un poco más que eso, porque la configuración de SFTP está un poco mezclada aquí. Por el momento, ignore SFTPConnection
y _sftp
aplazado.
ClientOptions
es básicamente un diccionario de lujo que connect
quiere ver a qué se conecta para poder verificar la clave de host.
SSHUserAuthClient
es el objeto que define cómo se realizará la autenticación. Esta clase sabe cómo probar las cosas habituales, como mirar ~/.ssh
y hablar con un agente local de SSH. Si desea cambiar cómo se realiza la autenticación, este es el objetivo para jugar. Puede subclase SSHUserAuthClient
y anular sus métodos getPassword
, getPublicKey
, getPrivateKey
y/o signData
, o puede escribir su propia clase completamente diferente que tenga cualquier otra lógica de autenticación que desee. Eche un vistazo a la implementación para ver qué métodos utiliza la implementación del protocolo SSH para obtener la autenticación.
Así que esta función configurará una conexión SSH y la autenticará. Una vez hecho esto, la instancia SFTPConnection
entra en juego. Observe cómo SSHUserAuthClient
toma la instancia SFTPConnection
como argumento.Una vez que la autenticación se realiza correctamente, se libera el control de la conexión a esa instancia. En particular, esa instancia tiene serviceStarted
llamado. Aquí está la aplicación plena de la clase SFTPConnection
:
class SFTPConnection(SSHConnection):
def serviceStarted(self):
self.openChannel(SFTPSession())
muy simple: todo lo que hace es abrir un nuevo canal. La instancia SFTPSession
que pasa interactúa con ese nuevo canal. He aquí cómo he definido 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)
Al igual que con SFTPConnection
, esta clase tiene un método que se llama cuando la conexión está preparada para ello. En este caso, se invoca cuando el canal se abre con éxito y el método es channelOpen
.
Por fin, los requisitos para el lanzamiento del subsistema SFTP están en su lugar. Entonces, channelOpen
envía una solicitud por el canal para iniciar ese subsistema. Pide una respuesta para que pueda decir cuándo ha tenido éxito (o ha fallado). Agrega una devolución de llamada al Deferred
para conectar un FileTransferClient
consigo mismo.
La instancia FileTransferClient
realmente formateará y analizará los bytes que se mueven a través de este canal de la conexión. En otras palabras, es una implementación de solo el protocolo SFTP. Se ejecuta sobre el protocolo SSH, que los otros objetos que este ejemplo ha creado se ocupan de. Pero en lo que a él respecta, recibe bytes en su método dataReceived
, los analiza y envía datos a las devoluciones de llamada, y ofrece métodos que aceptan objetos estructurados de Python, formatea esos objetos como los bytes correctos y los escribe en su transporte.
Sin embargo, nada de eso es directamente importante para su uso. Sin embargo, antes de dar un ejemplo de cómo realizar acciones de SFTP con él, cubramos ese atributo _sftp
. Este es mi enfoque crudo para hacer que esta instancia recientemente conectada FileTransferClient
esté disponible para algún otro código que realmente sepa qué hacer con él. Separar el código de configuración de SFTP del código que realmente usa la conexión SFTP hace que sea más fácil reutilizar el primero al cambiar el último.
Así que el Deferred
I en sftp
se dispara con el FileTransferClient
conectado en _cbSFTP
. Y la persona que llama de sftp
consiguió que Deferred
volvió a ellos, por lo que el código puede hacer cosas como esta:
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)
Así primeros sftp
conjuntos de toda la conexión, todo el camino hasta la conexión de un FileTransferClient
instancia local hasta un byte secuencia que tiene algún subsistema SFTP del servidor SSH en el otro extremo, y luego transfer
toma esa instancia y la usa para hacer un directorio, usando uno de los métodos de FileTransferClient
para realizar alguna operación SFTP.
Aquí hay un listado de código completo que usted debería ser capaz de ejecutar y para ver un directorio creado en algún servidor 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
es una operación bastante sencilla. El método makeDirectory
devuelve un Deferred
que se dispara cuando se ha creado el directorio (o si hay un error al hacerlo). La transferencia de un archivo es un poco más complicada, ya que debe proporcionar los datos para enviar, o definir cómo se interpretarán los datos recibidos si está descargando en lugar de cargar.
Si lees las cadenas de docs para los métodos de FileTransferClient
, deberías ver cómo usar sus otras características: para la transferencia de archivos real, openFile
es de especial interés. Le da un Deferred
que se activa con un proveedor ISFTPFile. Este objeto tiene métodos para leer y escribir contenido de archivos.
¿Puedes explicarme cómo estás atrapado más específicamente? Como su pregunta es ahora, la única forma en que puedo pensar para responder es escribir un tutorial completo de Conch/SFTP, que probablemente sea más trabajo que 15 puntos en SO (al menos en este momento). ;) Pero una pregunta más específica podría tener una respuesta más simple. –
@ Jean-Paul En este momento, creo, necesito subclase t.c.s.f. FileTransferClient. También creo que necesito abrir una conexión SSH similar al ejemplo que he vinculado anteriormente. Estoy atascado con cómo subclasificar correctamente t.c.s.f. FileTransferClient y cómo mover realmente los archivos. No es necesario un tutorial completo ya que estoy interesado en aprender retorcido (este fue mi primer proyecto pequeño) pero un boceto de qué métodos y clases debería usar o leer, o incluso un ejemplo simple en los documentos (lo encontré cftp.py difícil de leer) sería muy apreciado. – rymurr