Play2でのファイルダウンロード処理

Webアプリケーションで巨大なファイルのダウンロードを行う場合はダウンロードするコンテンツをすべてメモリ上に読み込むのではなく、少しずつ読み込みながら逐次レスポンスを書き出していく必要があります。Play2ではこのような場合、Enumeratorを使ってレスポンスを返却します。

サーバ上のファイルを直接返却する場合は以下のようにEnumerator#fromFile()メソッドを使用します。

def download = Action {
  val file = new java.io.File("/tmp/fileToServe.pdf")
  val fileContent: Enumerator[Array[Byte]] = Enumerator.fromFile(file)    
  
  SimpleResult(
    header = ResponseHeader(200, Map(CONTENT_LENGTH -> file.length.toString)),
    body = fileContent
  )
}

ダウンロードするコンテンツを動的に生成する場合、たとえばDBのカーソルを回しながら巨大なCSVを出力する場合は以下のようにEnumerator#outputStream()メソッドを使用するとOutputStreamに書き出した内容をChannelに吐き出すEnumeratorを作成することができます。

def download = Action {
  SimpleResult(
    header = ResponseHeader(200),
    body = Enumerator.outputStream { out =>
      try {
        getContentsAsStream().foreach { x =>
          out.write(s"${x.id},${x.subject},${x.author},${x.date}".getBytes("UTF-8"))
        }
      } finally {
        out.close()
      }
    }
  )
}

Enumeratorとか出てくると身構えてしまいますが、やり方さえわかればそんなに難しいことはないかなーと思います。