2010-11-29 50 views
1

有没有人设法在twisted的FTP服务器上实现REST命令?我目前的尝试:在twisted.protocols.ftp.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命令后面,为什么套接字超时?

更新:

启用日志记录由让 - 保罗·Calderone的的建议后,它看起来像REST命令甚至没有做它到我的FTP类的DTP连接超时之前从没有连接(时间戳减少到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命令。

回答

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方法有时可能片状,但它工作的大部分时间。很明显,请自行承担风险。

2

确认客户端的行为正如您所期望的那样。使用tcpdump或wireshark捕获所有相关流量是一种很好的方法,尽管您也可以通过多种方式(例如,使用工厂包装器twisted.protocols.policies.TrafficLoggingFactory)在基于Twisted的FTP服务器中启用日志记录功能。

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

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

+0

启用日志记录后,它看起来像是阻塞的DTP连接(来自PSV命令)。客户端发送PSV,然后发送REST(然后发送RETR,我想,尽管它从来没有那么远)。看起来问题是客户端在发送REST(并等待响应)之前未连接到DTP,并将它们发送到等待的循环中。他们正在等待对REST的响应,并且正在等待DTP连接。我想这就是为什么REST没有默认实现。人物。 – eternicode 2010-12-02 23:05:40

+1

有些东西阻塞了更多命令的处理过程 - 尽管它并不阻塞,因为它阻止了任何其他代码在反应器线程中运行。 `FTP.lineReceived`首先调用`pauseProducing`。这使得反应器停止读取其连接,直到调用“resumeProducing”为止。而`FTP`只在确定已完全处理完该行后调用`resumeProducing`。在PASV的情况下,“完全处理”意味着客户端已*连接到DTP端口。所以你是对的,由于FTP协议目前正在实施,REST无法工作。 – 2010-12-03 02:29:19