我正在尝试使用扭曲的海螺在python中实现一个非常简单的文件传输客户端。客户端应该简单地以编程方式将一些文件传输到远程ssh / sftp服务器。该功能提供了用户名,密码,文件列表,目标服务器:目录,只需要以跨平台的方式进行身份验证和复制。
我已经阅读了有关Twisted的一些入门资料,并设法制作了自己的SSH客户端,该客户端仅cat在远程服务器上执行。我很难将其扩展到移动文件。我看了cftp.py和文件传输测试,但完全被扭曲弄得迷惑不解。
cat
有没有人能给我正确的方向的建议或参考?我已经构建的SSH客户端就是基于这个客户端的。
使用Twisted Conch进行SFTP文件传输涉及两个不同的阶段(嗯,如果斜视,它们是不同的)。基本上,首先,您需要建立一个连接,并在其上打开一个通道,并在其上运行sftp子系统。ew。然后,您可以使用连接到该通道的FileTransferClient实例的方法来执行您要执行的SFTP操作。
Twisted.conch.client软件包中的模块提供的API可以帮助您建立SSH连接。这是一个twisted.conch.client.default.connect在一个不太令人惊讶的界面中包裹了一点怪异功能的函数:
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递延。
SFTPConnection
_sftp
ClientOptions基本上只是一本花哨的字典,connect希望能够看到它所连接的内容,以便可以验证主机密钥。
ClientOptions
connect
SSHUserAuthClient是定义身份验证如何进行的对象。此类知道如何尝试常见的事情,例如查看~/.ssh本地SSH代理并与之交谈。如果要更改身份验证的方式,则可以使用该对象。你也可以继承SSHUserAuthClient并覆盖其getPassword,getPublicKey,getPrivateKey,和/或signData方法,或者你可以写有你想要的任何其他身份验证逻辑自己完全不同的类。查看实现,以查看SSH协议实现调用哪种方法来完成身份验证。
SSHUserAuthClient
~/.ssh
getPassword
getPublicKey
getPrivateKey
signData
因此,此功能将建立SSH连接并进行身份验证。完成之后,SFTPConnection实例开始起作用。注意如何SSHUserAuthClient将SFTPConnection实例作为参数。身份验证成功后,它将移交对该实例连接的控制。特别是,该实例已对其进行了serviceStarted调用。这是SFTPConnection该类的完整实现:
serviceStarted
class SFTPConnection(SSHConnection): def serviceStarted(self): self.openChannel(SFTPSession())
很简单:要做的就是打开一个新频道。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。
channelOpen
最后,启动SFTP子系统的要求已经到位。因此,channelOpen通过通道发送请求以启动该子系统。它要求答复,以便可以知道何时成功(或失败)。它增加了一个回调到Deferred它会挂钩一个FileTransferClient给自己。
Deferred
FileTransferClient
该FileTransferClient实例将实际格式化和解析在连接的此通道上移动的字节。换句话说,它 只是 SFTP协议的一种实现。它运行在SSH协议上,此示例创建的其他对象都在使用该协议。但是就其而言,它在其dataReceived方法中接收字节,对其进行解析并将数据分派给回调,并且它提供了一些方法,这些方法接受结构化的Python对象,将这些对象格式化为正确的字节,并将其写入其传输中。
dataReceived
不过,这些都不是直接使用它的重要意义。但是,在给出如何使用它执行SFTP操作的示例之前,让我们介绍一下该_sftp属性。这是我使该新连接的FileTransferClient实例可用于其他一些实际上知道如何处理它的代码的粗略方法。将SFTP设置代码与实际使用SFTP连接的代码分开,可以更轻松地重用前者,同时更改后者。
因此,Deferred我设置为insftp的FileTransferClient连接被解雇了_cbSFTP。然后sftpget的调用者Deferred返回给他们,以便代码可以执行以下操作:
sftp
_cbSFTP
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实例连接到字节流,该字节流的另一端具有一些SSH服务器的SFTP子系统,然后transfer使用该实例并使用它来创建目录,的方法FileTransferClient进行一些SFTP操作。
transfer
这是一个完整的代码列表,您应该可以运行该代码,并查看在某些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在创建目录时触发(或在执行此操作时发生错误)时触发的。传输文件要复杂得多,因为您必须提供要发送的数据,或者定义在下载而不是上传时如何解释接收到的数据。
makeDirectory
但是,如果您阅读docstring的方法FileTransferClient,则应该了解如何使用其其他功能- 对于实际文件传输,openFile主要是感兴趣的。它为您Deferred提供了一个使用ISFTPFile提供程序触发的功能。该对象具有读取和写入文件内容的方法。
openFile