我正在尝试使用twisted来实现一个限制上传文件大小的FTP服务器。理想情况下,这将在传输开始之前发生,但如果它在传输过程中正常退出则不是真正的问题。如果它太大。
我已经从非常基本的ftpserver.py开始,慢慢地从ftp.py中获取更多底层类,以便进入内部。
下面的当前代码,请原谅我所使用的'破解和削减'风格,直到我能够使其运作。
#!/usr/bin/python
import os
from twisted.protocols.ftp import FTPFactory, FTPShell, FTPAnonymousShell, IFTPShell
from twisted.cred.portal import Portal
from twisted.cred.checkers import AllowAnonymousAccess
from twisted.internet import reactor, defer
from twisted.python import filepath, failure
class FileConsumer1(object):
def __init__(self, fObj):
self.fObj = fObj
def registerProducer(self, producer, streaming):
self.producer = producer
assert streaming
def unregisterProducer(self):
self.producer = None
self.fObj.close()
def write(self, bytes):
size = os.fstat(self.fObj.fileno()).st_size + len(bytes)
if size > 10:
raise Exception("File too large") # WHAT GOES HERE?
self.fObj.write(bytes)
class FileWriter1(object):
def __init__(self, fObj):
self.fObj = fObj
self._receive = False
def receive(self):
assert not self._receive, "Can only call IWriteFile.receive *once* per instance"
self._receive = True
return defer.succeed(FileConsumer1(self.fObj))
def close(self):
return defer.succeed(None)
class FTPShell1(FTPShell):
def openForWriting(self, path):
p = self._path(path)
if p.isdir():
return defer.fail(IsADirectoryError(path))
try:
fObj = p.open('w')
except (IOError, OSError), e:
return errnoToFailure(e.errno, path)
except:
return defer.fail()
return defer.succeed(FileWriter1(fObj))
class FTPRealm1(object):
def __init__(self, root):
self.path = filepath.FilePath(root)
def requestAvatar(self, avatarId, mind, *interfaces):
avatar = FTPShell1(self.path)
return (IFTPShell, avatar, getattr(avatar, 'logout', lambda: None))
p = Portal(FTPRealm1('./'), [ AllowAnonymousAccess() ])
f = FTPFactory(p)
reactor.listenTCP(4021, f)
reactor.run()
清楚地检查尺寸> 10会更大,但是如何指出此时存在问题?现在,扭曲捕获异常,但它不是很优雅。从我对ftp.py的检查可以看出,没有什么明显我可以回到这里。我可以通过某种方式传递延期吗?我该如何优雅地关闭转移?
谢谢,
这是修订版
#!/usr/bin/python
import os
from zope.interface import Interface, implements
from twisted.protocols.ftp import FTPFactory, FTPShell, FTPAnonymousShell, IFTPShell, IWriteFile , BaseFTPRealm, FTPCmdError, EXCEEDED_STORAGE_ALLOC
from twisted.cred.portal import Portal
from twisted.cred.checkers import AllowAnonymousAccess
from twisted.internet import reactor, defer, interfaces
from twisted.python import filepath
class ExceededStorageAllocError(FTPCmdError):
errorCode = EXCEEDED_STORAGE_ALLOC
class FileConsumer(object):
implements(interfaces.IConsumer)
def __init__(self):
self.data = ""
self.error = None
def registerProducer(self, producer, streaming):
self.producer = producer
assert streaming
def unregisterProducer(self):
if self.producer:
self.producer.stopProducing()
self.producer = None
def write(self, bytes):
self.data += bytes
if len(self.data) > 10:
self.unregisterProducer()
self.error = ExceededStorageAllocError()
class FileWriter(object):
implements(IWriteFile)
def __init__(self, path):
self.path = path
def receive(self):
self.consumer = FileConsumer()
return defer.succeed(self.consumer)
def close(self):
if self.consumer.error:
return defer.fail(self.consumer.error)
try:
f = self.path.open('w')
except (IOError, OSError), e:
return errnoToFailure(e.errno, path)
f.write(self.consumer.data)
return defer.succeed(None)
class FTPShell1(FTPShell):
makeDirectory = FTPAnonymousShell.makeDirectory
removeDirectory = FTPAnonymousShell.removeDirectory
def openForWriting(self, path):
p = self._path(path)
if p.isdir():
return defer.fail(IsADirectoryError(path))
return defer.succeed(FileWriter(p))
class FTPRealm1(BaseFTPRealm):
def __init__(self, root):
self.root = root
def requestAvatar(self, avatarId, mind, *interfaces):
avatar = FTPShell1(filepath.FilePath(self.root))
return (IFTPShell, avatar, getattr(avatar, 'logout', lambda: None))
p = Portal(FTPRealm1('./'), [ AllowAnonymousAccess() ])
f = FTPFactory(p)
reactor.listenTCP(4021, f)
reactor.run()
在FileConsumer()中累积接收的数据,然后在文件太长时中止。然后,FileWriter()的close()方法报告该错误或将完整的缓冲区写入文件。
我遇到的唯一真正问题是,运行时,服务器上会显示异常:
Unexpected error received during transfer:
Traceback (most recent call last):
Failure: __main__.ExceededStorageAllocError:
答案 0 :(得分:1)
作为一个快速免责声明,我对Twisted的制作人/消费者模型非常不满意,所以这可能行不通。一如往常,如果事情爆发,我不负责任;)
你似乎正走在正确的道路上,所以你要自己拍拍自己。我想如果你在文件太大时调用unregisterProducer
,文件应该停止消费。您可能还需要致电self.producer.stopProducing()
,但请不要引用我。
def unregisterProducer(self):
self.producer.stopProducing()
self.fObj.close()
def write(self, bytes):
size = os.fstat(self.fObj.fileno()).st_size + len(bytes)
if size > 10:
self.unregisterConsumer()
# log statements would go here
# do some clean up too
self.fObj.write(bytes)
如果我的心理代码Python解释器是正确的,这应该只是停止使用该文件。至于你应该回到客户端的内容,你将不得不阅读关于FTP的RFC来解决这个问题。
<强> PS 强>
尽管看似乏味,但请使用@implementor
装饰器。大多数情况下你会没事,但可能会出现意外追踪的情况。