在twisted.protocols.ftp.FTP中实现REST?

时间:2010-11-29 15:23:21

标签: ftp twisted

有没有人设法在twisted的FTP服务器中实现REST命令?我目前的尝试:

from twisted.protocols import ftp
from twisted.internet import defer

class MyFTP(ftp.FTP):
    def ftp_REST(self, pos):
        try:
            pos = int(pos)
        except ValueError:
            return defer.fail(CmdSyntaxError('Bad argument for REST'))

        def all_ok(result):
            return ftp.REQ_FILE_ACTN_PENDING_FURTHER_INFO # 350

        return self.shell.restart(pos).addCallback(all_ok)

class MyShell(ftp.FTPShell):
    def __init__(self, host, auth):
        self.position = 0
        ...

    def restart(self, pos):
        self.position = pos
        print "Restarting at %s"%pos
        return defer.succeed(pos)

当客户端发送REST命令时,在脚本输出中看到它之前需要几秒钟:

Traceback (most recent call last):
Failure: twisted.protocols.ftp.PortConnectionError: DTPFactory timeout
Restarting at <pos>

我做错了什么?在我看来,响应应该立即从REST命令开始,为什么套接字超时?

更新

按照Jean-Paul Calderone的建议启用日志记录后,看起来REST命令甚至没有在DTP连接因缺少连接而超时之前进入我的FTP类(为简洁起见,时间戳缩减为MM:SS) :

09:53 [TrafficLoggingProtocol,1,127.0.0.1] cleanupDTP
09:53 [TrafficLoggingProtocol,1,127.0.0.1] <<class 'twisted.internet.tcp.Port'> of twisted.protocols.ftp.DTPFactory on 37298>
09:53 [TrafficLoggingProtocol,1,127.0.0.1] dtpFactory.stopFactory
09:53 [-] (Port 37298 Closed)
09:53 [-] Stopping factory <twisted.protocols.ftp.DTPFactory instance at 0x8a792ec>
09:53 [-] dtpFactory.stopFactory
10:31 [-] timed out waiting for DTP connection
10:31 [-] Unexpected FTP error
10:31 [-] Unhandled Error
        Traceback (most recent call last):
        Failure: twisted.protocols.ftp.PortConnectionError: DTPFactory timeout

10:31 [TrafficLoggingProtocol,2,127.0.0.1] Restarting at 1024

ftp_PASV命令返回DTPFactory.deferred,其被描述为“当连接实例时将延迟[将]触发”。 RETR命令很好(否则ftp.FTP将毫无价值)。

这让我相信在这里存在某种阻塞操作,在进行DTP连接之前不会发生任何其他事情;然后我们才能接受进一步的命令。不幸的是,它看起来像一些(所有?)客户端(具体来说,我正在使用FileZilla进行测试)在尝试恢复下载时连接之前发送REST命令。

2 个答案:

答案 0 :(得分:2)

验证客户端是否按预期运行。使用tcpdump或wireshark捕获所有相关流量是一种很好的方法,尽管您也可以通过多种方式启用基于Twisted的FTP服务器的登录(例如,使用工厂包装器twisted.protocols.policies.TrafficLoggingFactory )。

从超时错误后跟“Restarting ...”日志消息,我猜测客户端先发送RETR''然后是REST。 RETR超时是因为客户端在收到对REST的响应之后才尝试连接到数据通道,并且Twisted服务器甚至在客户端连接到数据通道之后甚至不处理REST(并且下载整个文件)。修复此问题可能需要更改ftp.FTP处理来自客户端的命令的方式,以便可以正确解释跟踪RETR的REST(或者您正在使用的FTP客户端只是错误,来自我能找到的协议文档, RETR应该遵循REST,而不是相反的方式。)

这只是一个猜测,你应该看一下流量捕获来确认或拒绝它。

答案 1 :(得分:1)

在深入挖掘资源并摆弄思路之后,这就是我解决的解决方案:

class MyFTP(ftp.FTP):
  dtpTimeout = 30

  def ftp_PASV(self):
    # FTP.lineReceived calls pauseProducing(), and doesn't allow
    # resuming until the Deferred that the called function returns
    # is called or errored.  If the client sends a REST command
    # after PASV, they will not connect to our DTP connection
    # (and fire our Deferred) until they receive a response.
    # Therefore, we will turn on producing again before returning
    # our DTP's deferred response, allowing the REST to come
    # through, our response to the REST to go out, the client to
    # connect, and everyone to be happy.
    resumer = reactor.callLater(0.25, self.resumeProducing)
    def cancel_resume(_):
      if not resumer.called:
        resumer.cancel()
      return _
    return ftp.FTP.ftp_PASV(self).addBoth(cancel_resume)
  def ftp_REST(self, pos):
    # Of course, allowing a REST command to come in does us no
    # good if we can't handle it.
    try:
      pos = int(pos)
    except ValueError:
      return defer.fail(CmdSyntaxError('Bad argument for REST'))

    def all_ok(result):
      return ftp.REQ_FILE_ACTN_PENDING_FURTHER_INFO

    return self.shell.restart(pos).addCallback(all_ok)

class MyFTPShell(ftp.FTPShell):
  def __init__(self, host, auth):
    self.position = 0

  def restart(self, pos):
    self.position = pos
    return defer.succeed(pos)

callLater方法有时可能不稳定,但它在大多数情况下都有效。显然,使用风险自负。