问题描述
我正在尝试使用扭曲的海螺在 python 中实现一个非常简单的文件传输客户端.客户端应该以编程方式简单地将一些文件传输到远程 ssh/sftp 服务器.该函数被赋予用户名、密码、文件列表、目标服务器:目录,只需进行跨平台的认证和复制即可.
I am trying to implement a very simple file transfer client in python using twisted conch. The client should simply transfer a few files to a remote ssh/sftp server in a programatic way. The function is given username, password, file list, destination server:directory and just needs to carry out the authentication and copying in a cross-platform way.
我已经阅读了一些关于 Twisted 的介绍性材料,并设法制作了我自己的 SSH 客户端,它只在远程服务器上执行 cat
.我真的很难将其扩展到移动文件.我已经查看了 cftp.py 和文件传输测试,但完全被扭曲弄糊涂了.
I have read some introductory material on twisted and have managed to make my own SSH client which just executes cat
on the remote server. I am having a real difficult time extending this to moving the files over. I have taken a look at cftp.py and the filetransfer tests but am completely mystified by twisted.
有没有人有任何建议或参考可以为我指明正确的方向?我已经构建的 SSH 客户端基于 这个.
Does anyone have any suggestions or references that can point me in the right direction?The SSH client I have constructed already is based off this one.
推荐答案
使用 Twisted Conch 进行 SFTP 文件传输涉及几个不同的阶段(好吧,如果你眯着眼,它们是不同的).基本上,首先您需要建立一个连接,并在其上打开一个通道,并在其上运行一个 sftp 子系统.哇.然后你可以使用 FileTransferClient 实例连接到该通道以执行您想要执行的任何 SFTP 操作.
Doing an SFTP file transfer with Twisted Conch involves a couple distinct phases (well, they're distinct if you squint). Basically, first you need to get a connection set up with a channel open on it with an sftp subsystem running on it. Whew. Then you can use the methods of a FileTransferClient instance connected to that channel to perform whichever SFTP operations you want to perform.
twisted.conch.client 包.这是一个函数,它在一个稍微不那么令人惊讶的界面中包装了 twisted.conch.client.default.connect
的轻微奇怪之处:
The bare essentials of getting an SSH connection set up can be taken care of for you by APIs provided by modules in the twisted.conch.client package. Here's a function that wraps up the slight weirdness of twisted.conch.client.default.connect
in a slightly less surprising interface:
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 连接.
This function takes a username, hostname (or IP address), and port number and sets up an authenticated SSH connection to the server at that address using the account associated with the given username.
实际上,它的作用不止于此,因为 SFTP 设置在这里有点混杂.不过,暂时忽略 SFTPConnection
和 _sftp
Deferred.
Actually, it does a little more than that, because the SFTP setup is a little mixed in here. For the moment though, ignore SFTPConnection
and that _sftp
Deferred.
ClientOptions
基本上只是一个精美的字典,connect
希望能够看到它正在连接的内容,以便它可以验证主机密钥.
ClientOptions
is basically just a fancy dictionary that connect
wants to be able to see what it's connecting to so it can verify the host key.
SSHUserAuthClient
是定义身份验证将如何发生的对象.这个班级知道如何尝试通常的事情,比如查看 ~/.ssh
和与本地 SSH 代理交谈.如果您想更改身份验证的完成方式,这就是要玩的对象.您可以子类化 SSHUserAuthClient
并覆盖其 getPassword
、getPublicKey
、getPrivateKey
和/或 signData
方法,或者您可以编写自己的完全不同的类,该类具有您想要的任何其他身份验证逻辑.查看实现以了解 SSH 协议实现调用了哪些方法来完成身份验证.
SSHUserAuthClient
is the object that defines how the authentication will happen. This class knows how to try the usual things like looking at ~/.ssh
and talking to a local SSH agent. If you want to change how authentication is done, this is the object to play around with. You can subclass SSHUserAuthClient
and override its getPassword
, getPublicKey
, getPrivateKey
, and/or signData
methods, or you can write your own completely different class that has whatever other authentication logic you want. Take a look at the implementation to see what methods the SSH protocol implementation calls on it to get the authentication done.
所以这个函数会建立一个 SSH 连接并验证它.完成后,SFTPConnection
实例开始发挥作用.注意 SSHUserAuthClient
如何将 SFTPConnection
实例作为参数.身份验证成功后,它会将控制权移交给该实例.特别是,该实例调用了 serviceStarted
.下面是 SFTPConnection
类的完整实现:
So this function will set up an SSH connection and authenticate it. After that's done, the SFTPConnection
instance comes into play. Notice how SSHUserAuthClient
takes the SFTPConnection
instance as an argument. Once authentication succeeds, it hands off control of the connection to that instance. In particular, that instance has serviceStarted
called on it. Here's the full implementation of the SFTPConnection
class:
class SFTPConnection(SSHConnection):
def serviceStarted(self):
self.openChannel(SFTPSession())
很简单:它所做的就是打开一个新频道.它传入的 SFTPSession
实例可以与该新通道进行交互.这是我定义 SFTPSession
的方式:
Very simple: all it does is open a new channel. The SFTPSession
instance it passes in gets to interact with that new channel. Here's how I defined 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
.
Like with SFTPConnection
, this class has a method that gets called when the connection is ready for it. In this case, it's called when the channel is opened successfully, and the method is channelOpen
.
最后,启动 SFTP 子系统的要求已经到位.所以 channelOpen
通过通道发送一个请求来启动那个子系统.它要求回复,以便它可以判断何时成功(或失败).它向 Deferred
添加一个回调,它可以将 FileTransferClient
连接到自身.
At last, the requirements for launching the SFTP subsystem are in place. So channelOpen
sends a request over the channel to launch that subsystem. It asks for a reply so it can tell when that has succeeded (or failed). It adds a callback to the Deferred
it gets to hook up a FileTransferClient
to itself.
FileTransferClient
实例将实际格式化和解析通过该连接通道移动的字节.换句话说,它是只是 SFTP 协议的实现.它通过 SSH 协议运行,本示例创建的其他对象负责处理该协议.但就它而言,它在其 dataReceived
方法中接收字节,解析它们并将数据分派给回调,它提供了接受结构化 Python 对象的方法,将这些对象格式化为正确的字节,以及将它们写入其传输.
The FileTransferClient
instance will actually format and parse bytes that move over this channel of the connection. In other words, it is an implementation of just the SFTP protocol. It is running over the SSH protocol, which the other objects this example has created take care of. But as far as it is concerned, it receives bytes in its dataReceived
method, parses them and dispatches data to callbacks, and it offers methods which accept structured Python objects, formats those objects as the right bytes, and writes them to its transport.
不过,对于使用它而言,这些都不是直接重要的.但是,在举例说明如何使用它执行 SFTP 操作之前,让我们介绍一下 _sftp
属性.这是我使这个新连接的 FileTransferClient
实例可用于其他一些实际上知道如何处理它的代码的粗略方法.将 SFTP 设置代码与实际使用 SFTP 连接的代码分开,可以更轻松地重用前者,同时更改后者.
None of that is directly important to using it, though. However, before giving an example of how to perform SFTP actions with it, let's cover that _sftp
attribute. This is my crude approach to making this newly connected FileTransferClient
instance available to some other code which will actually know what to do with it. Separating the SFTP setup code from the code that actually uses the SFTP connection makes it easier to reuse the former while changing the latter.
因此,我在 sftp
中设置的 Deferred
被 _cbSFTP
中连接的 FileTransferClient
触发.sftp
的调用者将 Deferred
返回给他们,这样代码就可以做这样的事情:
So the Deferred
I set in sftp
gets fired with the FileTransferClient
connected in _cbSFTP
. And the caller of sftp
got that Deferred
returned to them, so that code can do things like this:
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 操作.
So first sftp
sets up the whole connection, all the way to connecting a local FileTransferClient
instance up to a byte stream which has some SSH server's SFTP subsystem on the other end, and then transfer
takes that instance and uses it to make a directory, using one of the methods of FileTransferClient
for performing some SFTP operation.
这是一个完整的代码清单,您应该能够运行并查看在某些 SFTP 服务器上创建的目录:
Here's a complete code listing that you should be able to run and to see a directory created on some SFTP server:
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
is a fairly simple operation. The makeDirectory
method returns a Deferred
that fires when the directory has been created (or if there's an error doing so). Transferring a file is a little more involved, because you have to supply the data to send, or define how received data will be interpreted if you're downloading instead of uploading.
如果你阅读了 FileTransferClient
方法的文档字符串,你应该会看到如何使用它的其他功能 - 对于实际的文件传输,openFile
主要是感兴趣的.它为您提供了一个 Deferred
,它以 ISFTPFile 提供程序.该对象具有读取和写入文件内容的方法.
If you read the docstrings for the methods of FileTransferClient
, though, you should see how to use its other features - for actual file transfer, openFile
is mainly of interest. It gives you a Deferred
which fires with an ISFTPFile provider. This object has methods for reading and writing file contents.
这篇关于扭曲的海螺文件传输的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!