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

在twisted.protocols.ftp.ftp中实现REST?,ftp,twisted,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.

有人在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>
回溯(最近一次呼叫最后一次):
失败:twisted.protocols.ftp.PortConnectionError:DTPFactory超时
在重新启动
我做错了什么?在我看来,REST命令应该立即响应,为什么套接字超时

更新:

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

09:53[TrafficLoggingProtocol,1127.0.0.1]清理DTP
09:53[流量记录协议,1127.0.0.1]
09:53[TrafficLoggingProtocol,1127.0.0.1]dtpFactory.stopFactory
09:53[-](端口37298关闭)
09:53[-]停止工厂
09:53[-]dtpFactory.stopFactory
10:31[-]等待DTP连接时超时
10:31[-]意外的FTP错误
10:31[-]未处理的错误
回溯(最近一次呼叫最后一次):
失败:twisted.protocols.ftp.PortConnectionError:DTPFactory超时
10:31[TrafficLoggingProtocol,2127.0.0.1]在1024时重新启动
ftp\u PASV
命令返回
DTPFactory.deferred
,它被描述为“连接实例时将触发的延迟”。RETR命令可以顺利通过(否则ftp.ftp将毫无价值)


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

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

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


不过,这只是一个猜测,您应该查看流量捕获以确认或拒绝它。

在对源代码进行大量挖掘和修改后,我决定采用以下解决方案:

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方法有时可能很脆弱,但它在大多数情况下都有效。显然,使用时要自担风险。

启用日志记录后,似乎是DTP连接(来自PSV命令)阻塞了。客户机先发送PSV,然后发送REST(我想,然后再发送RETR,尽管它从来没有走那么远)。看起来问题在于客户端在发送REST(并等待响应)之前没有连接到DTP,从而将我们两个发送到一个等待循环中。他们在等待REST的响应,我在等待DTP连接。我想这就是REST在默认情况下没有实现的原因。有一些东西阻碍了进一步命令的处理——尽管它不会阻止任何其他代码在reactor线程中运行
FTP.lineReceived
以调用
pauseProducing
开始。这将使反应器停止从其连接读取数据,直到调用
resume
。而
FTP
仅在确定已完全处理该行后调用
resumeproducting
。对于PASV,“完全处理”表示客户端已连接到DTP端口。所以你是对的,由于FTP协议目前已经实现,REST永远无法工作。我想我们可能也想用Twisted解决这个问题。你想在一张新票上描述这个问题吗?
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)