読者です 読者をやめる 読者になる 読者になる

Kuchitama Tech Note

はてな記法がいつまでたっても覚えられないので、はてなダイアリーからマークダウンが使えるこっちに引っ越してきました。

Play sendFileのfileNameパラメータの型

昨日の記事で、PlayのsendFileメソッドのとりあえずの使い方を紹介しましたが、その追加情報です。

昨日のサンプルでは

Ok.sendFile(
  content = java.io.File("hoge.csv"),
  fileName = _ => "fuga.csv"
)

このように書く事で、ダウンロードされるファイル名を指定出来ると説明したのですが、よくよく見るとfileName = _ => "hoge.csv"ってなんじゃこりゃと思いませんか?

少なくとも僕は思いました。 今日、@backpaper0さんにも、「あれなんなの?」って聞かれました。 ただ、昨日は力尽きて調べる気力がわかなかったので、今日追記情報を載せます。

まず、sendFileメソッドの実装なのですが、↓こんな感じになってます。

https://github.com/playframework/Play20/blob/master/framework/src/play/src/main/scala/play/api/mvc/Results.scala Statusクラス(Line618-633)

/**
     * Send a file.
     *
     * @param content The file to send
     * @param inline Use Content-Disposition inline or attachment.
     * @param fileName function to retrieve the file name (only used for Content-Disposition attachment)
     */
    def sendFile(content: java.io.File, inline: Boolean = false, fileName: java.io.File => String = _.getName, onClose: () => Unit = () => ()): SimpleResult[Array[Byte]] = {
      SimpleResult(
        header = ResponseHeader(OK, Map(
          CONTENT_LENGTH -> content.length.toString,
          CONTENT_TYPE -> play.api.libs.MimeTypes.forFileName(content.getName).getOrElse(play.api.http.ContentTypes.BINARY)
        ) ++ (if (inline) Map.empty else Map(CONTENT_DISPOSITION -> ("attachment; filename=" + fileName(content))))),
        Enumerator.fromFile(content) &> Enumeratee.onIterateeDone(onClose)
      )
    }

ポイントは、引数のfileNameが fileName:java.io.File=> String = _.getName となっているところです。

これを括弧とか足して書き直すと、 fileName : {java.io.File => String} = _.getName みたいな感じになります。

つまり、fileNameは、

  • java.io.Fileを引数にとり、Stringを返す関数で、
  • デフォルトでは、def func(file:java.io.File) = {file_.getName}という関数が適用される

引数である、ということになります。

で、sendFile関数内では、 (if (inline) Map.empty else Map(CONTENT_DISPOSITION -> ("attachment; filename=" + fileName(content))))) このように、sendFileの第一引数であるcontentをfileNameの引数に渡しているので、contentのファイル名がContent-Dispositionヘッダに追加されることになります。

そのため、任意のファイル名を使用したいときは、fileNameに単純にStringを指定するのでなく、'java.io.File => String'型の関数として、_ => "hoge.csv"のように、何か引数を受け取るけど無視して"hoge.csv"を返す関数を指定しないといけないようです。 fileNameという引数名の型が関数というのは若干違和感がありますが、実装的にはScalaの特徴的な機能をつかっていて「なるほど」という感じですね。