WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

チャンク形式でダウンロード

by WebSurfer 2016年12月14日 23:49

ASP.NET Web Forms アプリにおいて、ファイルをチャンク形式エンコーディングしてブラウザにダウンロードするサンプルを備忘録として書いておきます。

Fiddler で見た応答

チャンク形式エンコーディングとは HTTP/1.1 で定義されている方式で、送信したいデータを任意のサイズのチャンク(塊)に分割し、各々のチャンクにサイズ情報を付与するエンコード方式です。

メリットは、送信するデータを動的に作成していて、作成中は全体のサイズが分からないが、部分的にでも作成でき次第送信を始められるというところにあるようです。(一旦全データをバッファして全体のサイズを調べ、Content-Length に設定するということをしなくても済みます)

サーバーにある既存のファイルをダウンロードするような場合はファイルのサイズは分かっていますので、チャンク形式エンコーディングせずに Content-Length を設定してダウンロードする方がよさそうです。(なので、自分的にはチャンク形式エンコーディングの使い道はあまりなさそうですが、せっかく調べたので書いておきます)

ASP.NET Web Forms アプリでは、HttpResponse.OutputStream にチャンクを書き込んだら Flush することでそのチャンクがクライアント(ブラウザ)に送信されます。

具体的には以下のコードのようにします。Sig552T8.jpg は 30,903 バイトの jpeg 画像ファイルで、以下の .aspx ページをブラウザから要求すると、その画像データを 10,000 バイトずつチャンクに分けて送信するようになっています。

この記事の一番上の画像を見てください。Fiddler を使ってサーバーからの応答をキャプチャしたものですが、応答ヘッダの赤線で示した部分にチャンク形式エンコーディングになっていること、コンテンツの最初の部分 32 37 31 30(UTF-8 で 2710 ⇒ 10 進数に直すと 10000)に最初に送信されたチャンクのサイズが示されていることが分かります。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  protected void Page_Load(object sender, EventArgs e)
  {
    string folder = "~/images/";
    string filename = "Sig552T8.jpg";
    string path = Server.MapPath(folder + filename);
    FileInfo fileInfo = new FileInfo(path);

    if (fileInfo.Exists)
    {
      int chunkSize = 10000;
      Byte[] buffer = new Byte[chunkSize];
      Response.Clear();   

      using (FileStream stream = File.OpenRead(path))
      {
        long length = stream.Length;
        Response.ContentType = "image/jpeg";
        Response.AddHeader("Content-Disposition", 
                "attachment; filename=" + fileInfo.Name);

        // ここで Flush しても通知バーは表示されない。以下のコ
        // ードのコメントアウトを外すとそれが確認できます。
        // Response.Flush();
        // System.Threading.Thread.Sleep(10000);

        while (length > 0 && Response.IsClientConnected)
        {
          int lengthRead = stream.Read(buffer, 0, chunkSize);
          Response.OutputStream.Write(buffer, 0, lengthRead);

          // ここでの最初の Flush で通知バーが表示される
          Response.Flush();
          length -= lengthRead;

          // chunked ダウンロードされていることを確認するため
          // 試験的に入れたコード。コメントアウトを外すとここ
          // で 5 秒待つ。
          // System.Threading.Thread.Sleep(5000);
        }
      }

      // <!DOCTYPE html ... 以下の html ソースをダウンロード
      // させないために設定
      Response.SuppressContent = true;
    }        
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    </form>
</body>
</html>

ブラウザ上ではどういう動きになるかと言うと、IE9 を使った場合ですが、上記コードの中の while ループの最初の Flush で以下の画像のように通知バーが表示されます。

IE9 の通知バー

上の通知バーの[ファイルを開く(O)]または[保存(S)]をクリックすると通知バーは以下の画像のように変わります。(注:Thread.Sleep(5000) のコメントアウトを外して試してください)

ダウンロード中の通知バー

その後、最初に表示された通知バーで[ファイルを開く(O)]をクリックしていた場合はダウンロードが完了すると画像 Sig552T8.jpg がブラウザ上に表示されます。

最初に表示された通知バーで[保存(S)]をクリックしていた場合はダウンロードが完了すると通知バーは以下の画像のように変わります。

ダウンロード完了の通知バー

[ファイルを開く(O)]または[フォルダーを開く(P)]でダウンロードした画像ファイルを確認できます。

Tags:

Upload Download

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Tag cloud

    Calendar

    <<  2017年11月  >>
    2930311234
    567891011
    12131415161718
    19202122232425
    262728293012
    3456789

    View posts in large calendar