web-dev-qa-db-ja.com

ASP.NET Responseで大きなファイルを配信する方法は?

私はデータベースからファイルコンテンツをストリーミングする代替手段を探していません、実際に問題の根本を探しています、これはファイルを実行していましたIIS 6ここでクラシックモードでアプリを実行しましたIIS= 7にアップグレードし、パイプラインモードでアプリプールを実行し、この問題が発生しました。

クライアントリクエストに大きなファイルを配信するハンドラーがあります。そして、私は次の問題に直面しています、

ファイルの平均サイズは4〜100 MBであるため、80 MBのファイルダウンロードの場合を考えてみましょう。

バッファリングオン、スロースタート

Response.BufferOutput = True;

これにより、ファイルの起動が非常に遅くなります。ユーザーのダウンロードやプログレスバーも数秒、通常は3〜20秒まで表示されないため、理由はIIS最初にファイル全体を読み取り、ファイルはビデオプレーヤーで再生されており、非常に低速で実行されますが、iPadはファイルの一部のみを最初にダウンロードするため、高速に動作します。

バッファリングオフ、コンテンツ長なし、ファストスタート、進捗なし

Reponse.BufferOutput = False;

これにより、すぐに開始されますが、エンドクライアント(Chromeなどの一般的なブラウザー)はContent-LengthをIISも知らないため、進行状況を表示せず、代わりにX KBがダウンロードされたと表示します。

バッファリングオフ、手動コンテンツ長、ファストスタート、進行状況、プロトコル違反

Response.BufferOutput = False;
Response.AddHeader("Content-Length", file.Length);

これにより、Chromeなどでの正しい即時ファイルダウンロードが行われますが、場合によってはIISハンドラーは「リモートクライアントの接続を閉じました」エラーになります(これは非常に頻繁です) これは、すべてのリクエストではなく、すべてのリクエストの5〜10%で発生します。

何が起こっているのでしょうか?IISはバッファリングをしないと100クライアントと呼ばれるものを送信せず、クライアントは出力を期待せずに切断する可能性があります。ただし、ソースからのファイルの読み込みには時間がかかりますが、クライアント側でタイムアウトを増やしましたが、IIS timesoutのようで、制御できません。

とにかくResponseに100を送信させ、だれにも接続を閉じさせないように強制することができますか

[〜#〜] update [〜#〜]

Firefox/Chromeで次のヘッダーを見つけましたが、ここではプロトコル違反または不良ヘッダーについては何も異常ではないようです。

Access-Control-Allow-Headers:*
Access-Control-Allow-Methods:POST, GET, OPTIONS
Access-Control-Allow-Origin:*
Access-Control-Max-Age:1728000
Cache-Control:private
Content-Disposition:attachment; filename="24.jpg"
Content-Length:22355
Content-Type:image/pjpeg
Date:Wed, 07 Mar 2012 13:40:26 GMT
Server:Microsoft-IIS/7.5
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET

更新2

リサイクルを回してもまだ多くは提供されませんでしたが、MaxWorkerProcessを8に増やしたので、以前よりもエラーの数が少なくなりました。

しかし、平均して、1秒間に200リクエストのうち、2〜10リクエストが失敗します。これは、ほぼ1秒おきに発生します。

更新

「サーバーがプロトコル違反をコミットしました。セクション= ResponseStatusLine」で失敗するリクエストの5%を続けて、WebClientを使用するWebサーバーからコンテンツをダウンロードする別のプログラムがあります。リクエストの5%が失敗します。とにかくWebClientの障害をトレースする方法はありますか?

問題の再定義

受信したゼロバイトファイル

IISは何らかの理由で接続を閉じます。WebConfigのクライアント側で、0バイトではないファイルに対して0バイトを受け取ります。SHA1ハッシュチェックを行い、IIS Webサーバー、エラーは記録されません。

これは私の間違いであり、Entity Frameworkを使用することで解決し、読み取りがトランザクションスコープにないためダーティ(コミットされていない行)を読み取り、トランザクションスコープに入れることでこの問題は解決しました。

プロトコル違反例外が発生

WebClientは、「サーバーがプロトコル違反をコミットしました。セクション= ResponseStatusLine。」と言ってWebExceptionをスローします。

安全でないヘッダー解析を有効にできることはわかっていますが、それがポイントではありません。適切なヘッダーを送信しているのがHTTPハンドラーである場合、なぜIIS 、異常なことは何もありません)、これはたった2%の頻度で発生します。

更新4

Sc-win32 64エラーが見つかり、MinBytesPerSecondのWebLimitsを240から0に変更する必要があることをどこかで読みましたが、それでもすべて同じです。ただし、IIS 64 sc-win32エラーをログに記録するたびに、IISはHTTPステータスを200として記録しますが、何らかのエラーが発生しました。大量のファイルが生成されるため、200のトレースロギング。

上記の問題は両方ともMinBytesPerSecondを増やすことで解決され、セッションを無効にするだけでなく、すべてのポイントを要約した詳細な回答を追加しました。

41
Akash Kava

IISで大きなファイルを配信する正しい方法は次のオプションですが、

  1. WebLimitsでMinBytesPerSecondをゼロに設定します(これにより、IISはより小さいサイズの転送でキープアライブ接続を保持しているクライアントを閉じることを選択します)
  2. より多くのワーカープロセスをアプリケーションプールに割り当てます。8に設定しました。これは、サーバーがより大きなファイルを配布している場合にのみ行う必要があります。これにより、確かに他のサイトのパフォーマンスが低下しますが、これにより配信が向上します。このサーバーにはWebサイトが1つしかなく、巨大なファイルを配信するだけなので、8に設定しました。
  3. アプリプールのリサイクルをオフにする
  4. セッションをオフにする
  5. バッファリングをオンのままにする
  6. 以下の各ステップの前に、Response.IsClientConnectedがtrueであるかどうかを確認し、そうでない場合はあきらめて何も送信しないでください。
  7. ファイルを送信する前にContent-Lengthを設定します
  8. 応答をフラッシュする
  9. 出力ストリームに書き込み、定期的にフラッシュ
12
Akash Kava

BufferOutputでコンテンツの長さをfalseに設定した場合、失敗の考えられる理由は、IIS送信するファイルをgzipしようとし、Content-Length IISは圧縮されたものに戻すことができず、エラーが始まります(*)。

そう keep the BufferOutput to false, and second disable the gzip from iis for the files you send-または、すべてのファイルのiis gzipを無効にして、gzip部分をプログラムで処理し、送信するファイルをgzipから除外します。

同じ理由でいくつかの同様の質問: ASP.NETサイトは時々フリーズしたり、ロード中にページの上部に奇妙なテキストを表示したり、負荷分散されたサーバーで

HTTP圧縮:一部の外部スクリプト/ CSSが時々適切に解凍しない

(*)もう一度変更しないのはなぜですか? IISでこのオプションを有効にし、ヘッダーがすべてブラウザに送信される準備が整っていない場合を除いて、ヘッダーを設定した瞬間から参加することはできません。

ファローアップ

Gzipされていない場合、次に思い浮かぶのは、ファイルが送信され、何らかの理由で接続が遅延し、タイムアウトして閉じられたことです。そのため、「リモートホストは接続を閉じました」を取得します。

これは原因に応じて解決できます。

  1. クライアントは本当に接続を閉じました
  2. ハンドラーを使用する場合、タイムアウトはページ自体からのものです(再び、おそらく、メッセージは "Page Timed Out"でなければなりません)。
  3. タイムアウトはアイドル待機から発生し、ページは実行時間以上かかり、タイムアウトを取得して接続を閉じます。この場合、メッセージはページタイムアウトであった可能性があります。
  4. ファイルを送信すると、プールはリサイクルを行います。すべてのプールのリサイクルを無効にします!これは私が今考えることができる最も可能性のあるケースです。

IISからの場合は、Webサイトのプロパティに移動して、最大の「接続タイムアウト」と「HTTPキープアライブを有効にする」を設定してください。

Web.configを変更することによるページタイムアウト(1つの特定のページに対してのみプログラムで変更できます)

<httpRuntime executionTimeout="43200"

以下もご覧ください: http://weblogs.asp.net/aghausman/archive/2009/02/20/prevent-request-timeout-in-asp-net.aspx

セッションロック

もう1つ確認する必要があるのは、ファイルを送信するために使用するハンドラーでセッションを使用しないことです。セッションが完了するまでアクションをロックし、ユーザーがファイルをダウンロードするのにより長い時間がかかると、2番目のタイムアウト。

いくつかの相対的な:

ランダムに遅い画像を返すためにaspxページを呼び出します

ASP.Netのセッションを完全に置き換える

Response.WriteFile関数は失敗し、504ゲートウェイタイムアウトが発生します

11
Aristos

あまり知られていないASP.NET Response.TransmitFile メソッドを使用します。これは非常に高速で(そしておそらくIISカーネルキャッシュ)を使用し、 Windowsのアンマネージド TransmitFile APIに基づいています。

ただし、このAPIを使用するには、転送する物理ファイルが必要です。架空のmyCacheFilePath物理ファイルパスを使用してこれを行う方法を説明する擬似c#コードを次に示します。クライアントキャッシングの可能性もサポートします。もちろん、既に手元にファイルがある場合は、そのキャッシュを作成する必要はありません。

    if (!File.Exists(myCacheFilePath))
    {
        LoadMyCache(...); // saves the file to disk. don't do this if your source is already a physical file (not stored in a db for example).
    }

    // we suppose user-agent (browser) cache is enabled
    // check appropriate If-Modified-Since header
    DateTime ifModifiedSince = DateTime.MaxValue;
    string ifm = context.Request.Headers["If-Modified-Since"];
    if (!string.IsNullOrEmpty(ifm))
    {
        try
        {
            ifModifiedSince = DateTime.Parse(ifm, DateTimeFormatInfo.InvariantInfo);
        }
        catch
        {
            // do nothing
        }

        // file has not changed, just send this information but truncate milliseconds
        if (ifModifiedSince == TruncateMilliseconds(File.GetLastWriteTime(myCacheFilePath)))
        {
            ResponseWriteNotModified(...); // HTTP 304
            return;
        }
    }

    Response.ContentType = contentType; // set your file content type here
    Response.AddHeader("Last-Modified", File.GetLastWriteTimeUtc(myCacheFilePath).ToString("r", DateTimeFormatInfo.InvariantInfo)); // tell the client to cache that file

    // this API uses windows lower levels directly and is not memory/cpu intensive on Windows platform to send one file. It also caches files in the kernel.
    Response.TransmitFile(myCacheFilePath)
5
Simon Mourier

このコードは、私にとってはうまくいきます。クライアントへのデータストリームをすぐに開始します。ダウンロード中に進行状況が表示されます。 HTTPに違反しません。 Content-Lengthヘッダーが指定され、チャンク転送エンコードは使用されません。

protected void PrepareResponseStream(string clientFileName, HttpContext context, long sourceStreamLength)
{
    context.Response.ClearHeaders();
    context.Response.Clear();

    context.Response.ContentType = "application/pdf";
    context.Response.AddHeader("Content-Disposition", string.Format("filename=\"{0}\"", clientFileName));

    //set cachebility to private to allow IE to download it via HTTPS. Otherwise it might refuse it
    //see reason for HttpCacheability.Private at http://support.Microsoft.com/kb/812935
    context.Response.Cache.SetCacheability(HttpCacheability.Private);
    context.Response.Buffer = false;
    context.Response.BufferOutput = false;
    context.Response.AddHeader("Content-Length", sourceStreamLength.ToString    (System.Globalization.CultureInfo.InvariantCulture));
}

protected void WriteDataToOutputStream(Stream sourceStream, long sourceStreamLength, string clientFileName, HttpContext context)
{
    PrepareResponseStream(clientFileName, context, sourceStreamLength);
    const int BlockSize = 4 * 1024 * 1024;
    byte[] buffer = new byte[BlockSize];
    int bytesRead;
    Stream outStream = m_Context.Response.OutputStream;
    while ((bytesRead = sourceStream.Read(buffer, 0, BlockSize)) > 0)
    {
        outStream.Write(buffer, 0, bytesRead);
    }
    outStream.Flush();
}
2
HANiS