Ответ 1
Выполнение передачи файлов 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-сервера SFTP на другом конце, а затем 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
, который запускается, когда каталог был создан (или если есть ошибка). Передача файла немного более активна, потому что вам нужно предоставить данные для отправки или определить, как полученные данные будут интерпретироваться, если вы загружаете, а не загружаете.
Если вы читаете docstrings для методов FileTransferClient
, тем не менее, вы должны увидеть, как использовать другие его функции - для фактической передачи файлов в основном интерес представляет openFile
. Он дает вам Deferred
, который запускается с помощью ISFTPFile. Этот объект имеет методы чтения и записи содержимого файла.