限制Twisted FTP服务器中的文件大小

时间:2017-03-20 17:23:44

标签: python ftp twisted

我正在尝试使用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: 

1 个答案:

答案 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装饰器。大多数情况下你会没事,但可能会出现意外追踪的情况。