在twisted.protocols.ftp.ftp中实现REST?
有人在twisted的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.
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)