2017-03-20 17 views
2

アップロードしたファイルのサイズを制限するtwistedを使用してFTPサーバーを実装しようとしています。理想的には、転送が開始される前にこれが行われるのが理想ですが、大きすぎると転送中に正常に終了すると問題にはなりません。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の検査から見る限り、私がここに戻ることは何も明らかではありません。何らかの方法で繰延べることはできますか?私はどのようにエレガントに転送を終了する必要がありますか?

おかげで、

はここで改訂版FileConsumer内に受信したデータを蓄積し

#!/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() 

()ファイルが長すぎる場合は、その後中止します。 FileWriter()のclose()メソッドは、そのエラーを報告するか、完全バッファをファイルに書き込みます。

私はこれを持ってるだけで本当の問題は、実行したときに、例外がサーバー上に表示されていることです:ツイストのプロデューサ/コンシューマモデルで、私は迅速免責事項として

Unexpected error received during transfer: 
Traceback (most recent call last): 
Failure: __main__.ExceededStorageAllocError: 

答えて

1

非常に悪いです、これはうまくいかないかもしれません。いつものように、物事が爆発すると私は責任を負いません;)

あなたは正しい道にいるようです。ファイルが大きすぎるときに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デコレータを使用してください。ほとんどの場合、大丈夫ですが、予期しないトレースバックが現れる場合があります。

+0

こんにちは、私はしばらくの間ツイストを使用しているが、アイデアのおかげで、これは私がプロデューサー/消費者モデルに接触して初めてです。私はあなたの提案を試みた、FileConsumer1()クラスunregisterCosumer()メソッドを持っていない - unregisterProducer()を意味するのですか? –

+0

うん、はい、私は 'unregisterProducer'を意味しました。答えも編集しました。 –

+0

あなたの提案を使用して修正された実装のための私の2番目のバージョンを参照してください。 –