14

Python/Twisted の Web アプリケーションで作業しています。

ユーザーが非常に大きなファイル (> 100 Mb) をダウンロードできるようにしたい。もちろん、すべてのファイルを (サーバーの) メモリにロードしたくはありません。

サーバー側私はこの考えを持っています:

...
request.setHeader('Content-Type', 'text/plain')
fp = open(fileName, 'rb')
try:
    r = None
    while r != '':
        r = fp.read(1024)
        request.write(r)
finally:
    fp.close()
    request.finish()

私はこれがうまくいくと思っていましたが、問題があります.FFでテストしています...ファイルのダウンロードが完了するまでブラウザが待たせているようで、開く/保存するダイアログボックスが表示されます。

ダイアログボックスがすぐに表示され、進行状況バーが表示されると思っていました...

たぶん、Httpヘッダーに何かを追加する必要があります...ファイルのサイズのようなものですか?

4

4 に答える 4

36

投稿したサンプル コードの 2 つの大きな問題は、非協調的であり、送信前にファイル全体をメモリにロードすることです。

while r != '':
    r = fp.read(1024)
    request.write(r)

Twisted は協調マルチタスキングを使用して、あらゆる種類の並行性を実現していることを思い出してください。したがって、このスニペットの最初の問題は、ファイル全体の内容に対する while ループであることです (これは大きいと言えます)。これは、ファイル全体がメモリに読み込まれ、プロセスで他の処理が発生する前に応答に書き込まれることを意味します。この場合、「何か」にはメモリ内バッファからネットワークへのバイトのプッシュも含まれるため、コードはファイル全体をメモリ内に一度に保持し、このループが完了したときにのみファイルの削除を開始します。 .

したがって、原則として、このようなループを使用して大きな仕事をする Twisted ベースのアプリケーションで使用するコードを作成するべきではありません。代わりに、イベント ループと連携する方法で、大きな仕事の各小さな部分を実行する必要があります。ネットワーク経由でファイルを送信する場合、これにアプローチする最良の方法は、プロデューサーコンシューマーを使用することです。これらは、バッファ空イベントを使用して大量のデータを効率的に移動し、不当な量のメモリを浪費することなく移動するための関連する 2 つの API です。

これらの API のドキュメントは次の場所にあります。

http://twistedmatrix.com/projects/core/documentation/howto/producers.html

幸いなことに、この非常に一般的なケースのために、独自に実装するのではなく、使用できる作成済みのプロデューサーもあります。

http://twistedmatrix.com/documents/current/api/twisted.protocols.basic.FileSender.html

おそらく、次のように使用する必要があります。

from twisted.protocols.basic import FileSender
from twisted.python.log import err
from twisted.web.server import NOT_DONE_YET

class Something(Resource):
    ...

    def render_GET(self, request):
        request.setHeader('Content-Type', 'text/plain')
        fp = open(fileName, 'rb')
        d = FileSender().beginFileTransfer(fp, request)
        def cbFinished(ignored):
            fp.close()
            request.finish()
        d.addErrback(err).addCallback(cbFinished)
        return NOT_DONE_YET

NOT_DONE_YET私のブログhttp://jcalderone.livejournal.com/50562.htmlで、「Twisted Web in 60 Seconds」シリーズの詳細とその他の関連するアイデアを読むことができます(特に「非同期応答」エントリを参照してください)。

于 2009-11-01T14:37:00.767 に答える
3

はい、Content-Length ヘッダーは、希望するプログレス バーを提供します!

于 2009-10-08T15:45:34.087 に答える
3

これが本当にコンテンツである場合は、クライアントがそれを処理できることを示すたびに、text/plainそれを送信することを真剣に検討する必要があります. Content-Encoding: gzip帯域幅が大幅に節約されるはずです。さらに、これが静的ファイルの場合、本当にやりたいことはsendfile(2). ダウンロードに関してブラウザが期待どおりに動作しない場合は、Content-Dispositionヘッダーを確認することをお勧めします。とにかく、ロジックは次のようになります。

クライアントがヘッダーgzipを介してエンコードを処理できることを示した場合Accept-Encoding(たとえばAccept-Encoding: compress;q=0.5, gzip;q=1.0、またはAccept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0同様のもの)、ファイルを圧縮し、圧縮された結果をどこかにキャッシュし、応答の正しいヘッダー ( Content-Encoding: gzipContent-Length: nContent-Type: text/plainなど) を書き込んでからsendfile(2)(ただし、または環境で利用可能になっていない可能性があります) を使用して、開いているファイル記述子からコンテンツを応答ストリームにコピーします。

受け入れない場合gzipは、同じことを行いますが、最初に gzip を実行しません。

または、Apache、Lighttpd、またはサーバーの前で透過プロキシとして機能する同様のものがある場合は、X-Sendfile非常に高速なヘッダーを使用できます。

response.setHeader('Content-Type', 'text/plain')
response.setHeader(
  'Content-Disposition',
  'attachment; filename="' + os.path.basename(fileName) + '"'
)
response.setHeader('X-Sendfile', fileName)
response.setHeader('Content-Length', os.stat(fileName).st_size)
于 2009-10-09T17:43:54.273 に答える
0

これは、ねじれた関数呼び出しの内部から使用できる urllib2 を使用して、チャンクでファイルをダウンロードする例です。

import os
import urllib2
import math

def downloadChunks(url):
    """Helper to download large files
        the only arg is a url
       this file will go to a temp directory
       the file will also be downloaded
       in chunks and print out how much remains
    """

    baseFile = os.path.basename(url)

    #move the file to a more uniq path
    os.umask(0002)
    temp_path = "/tmp/"
    try:
        file = os.path.join(temp_path,baseFile)

        req = urllib2.urlopen(url)
        total_size = int(req.info().getheader('Content-Length').strip())
        downloaded = 0
        CHUNK = 256 * 10240
        with open(file, 'wb') as fp:
            while True:
                chunk = req.read(CHUNK)
                downloaded += len(chunk)
                print math.floor( (downloaded / total_size) * 100 )
                if not chunk: break
                fp.write(chunk)
    except urllib2.HTTPError, e:
        print "HTTP Error:",e.code , url
        return False
    except urllib2.URLError, e:
        print "URL Error:",e.reason , url
        return False

    return file
于 2011-12-04T18:49:32.387 に答える