有没有人设法在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命令。
答案 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方法有时可能不稳定,但它在大多数情况下都有效。显然,使用风险自负。