Передача файлов SFTP с помощью Twisted Conch включает в себя несколько отдельных фаз (ну, они разные, если вы щуритесь). По сути, сначала вам нужно настроить соединение с открытым каналом, на котором работает подсистема sftp. Уф. Затем вы можете использовать методы экземпляра FileTransferClient , подключенного к этому каналу, для выполнения любых операций SFTP, которые вы хотите выполнить.
За простыми настройками SSH-соединения могут позаботиться о вас API, предоставляемые модулями в пакете twisted.conch.client . Вот функция, которая оборачивает небольшую странность twisted.conch.client.default.connect
в немного менее удивительном интерфейсе:
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
Эта функция принимает имя пользователя, имя хоста (или IP-адрес) и номер порта и устанавливает аутентифицированное SSH-соединение с сервером по этому адресу, используя учетную запись, связанную с данным именем пользователя.
На самом деле, это немного больше, потому что настройка SFTP здесь немного запутана. На данный момент, однако, игнорировать SFTPConnection
и это _sftp
Отложено.
ClientOptions
- это просто необычный словарь, который connect
хочет видеть, к чему он подключается, чтобы он мог проверить ключ хоста.
SSHUserAuthClient
- это объект, который определяет, как будет проходить аутентификация. Этот класс знает, как попробовать обычные вещи, такие как просмотр ~/.ssh
и общение с местным агентом SSH. Если вы хотите изменить способ аутентификации, вы можете поиграть с этим объектом. Вы можете создать подкласс SSHUserAuthClient
и переопределить его getPassword
, getPublicKey
, getPrivateKey
и / или signData
методы, или вы можете написать свой собственный совершенно другой класс, который имеет любую другую логику аутентификации, которую вы пожелаете. Взгляните на реализацию, чтобы увидеть, какие методы использует реализация протокола SSH для выполнения аутентификации.
Таким образом, эта функция установит соединение SSH и аутентифицирует его. После этого в игру вступает экземпляр SFTPConnection
. Обратите внимание, как SSHUserAuthClient
принимает экземпляр SFTPConnection
в качестве аргумента. Как только аутентификация прошла успешно, она передает контроль над подключением к этому экземпляру. В частности, для этого экземпляра вызывается serviceStarted
. Вот полная реализация класса SFTPConnection
:
class SFTPConnection(SSHConnection):
def serviceStarted(self):
self.openChannel(SFTPSession())
Очень просто: все, что он делает, это открывает новый канал. Экземпляр SFTPSession
, который он передает, получает возможность взаимодействовать с этим новым каналом. Вот как я определил 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)
Как и в SFTPConnection
, у этого класса есть метод, который вызывается, когда соединение готово к нему. В этом случае он вызывается при успешном открытии канала и имеет метод channelOpen
.
Наконец, требования для запуска подсистемы SFTP введены. Поэтому channelOpen
отправляет запрос по каналу для запуска этой подсистемы. Он запрашивает ответ, чтобы он мог сказать, когда это удалось (или не удалось). Он добавляет обратный вызов к Deferred
, который он получает, чтобы подключить FileTransferClient
к себе.
Экземпляр FileTransferClient
фактически отформатирует и проанализирует байты, которые перемещаются по этому каналу соединения. Другими словами, это реализация просто протокола SFTP. Он работает по протоколу SSH, о котором заботятся другие объекты, созданные в этом примере. Но что касается этого, он получает байты в своем методе dataReceived
, анализирует их и отправляет данные обратным вызовам и предлагает методы, которые принимают структурированные объекты Python, форматируют эти объекты как правильные байты и записывают их в свой транспорт. .
Ничто из этого не имеет прямого отношения к его использованию. Однако перед тем, как привести пример выполнения SFTP-действий с ним, давайте рассмотрим этот атрибут _sftp
. Это мой грубый подход к тому, чтобы сделать этот только что подключенный экземпляр FileTransferClient
доступным для некоторого другого кода, который фактически будет знать, что с ним делать. Отделение кода настройки SFTP от кода, который фактически использует соединение SFTP, упрощает повторное использование первого при изменении второго.
Таким образом, Deferred
, который я установил в sftp
, запускается с FileTransferClient
, подключенным в _cbSFTP
. И вызывающий sftp
получил, что Deferred
вернул им, так что код может делать такие вещи:
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)
Итак, сначала sftp
устанавливает все соединение, вплоть до подключения локального экземпляра FileTransferClient
до байтового потока, на другом конце которого находится подсистема SFTP сервера SSH, а затем transfer
- этот экземпляр. и использует его для создания каталога, используя один из методов FileTransferClient
для выполнения какой-либо операции SFTP.
Вот полный список кодов, который вы сможете запустить и увидеть каталог, созданный на каком-либо 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
- довольно простая операция. Метод makeDirectory
возвращает Deferred
, который срабатывает при создании каталога (или если при этом возникает ошибка). Передача файла немного сложнее, потому что вы должны предоставить данные для отправки или определить, как полученные данные будут интерпретироваться, если вы загружаете вместо загрузки.
Если вы прочитаете строки документации для методов FileTransferClient
, вы должны увидеть, как использовать другие его функции - для фактической передачи файлов, openFile
в основном представляет интерес. Он дает вам Deferred
, который запускается с ISFTPFile провайдером. Этот объект имеет методы для чтения и записи содержимого файла.