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の特徴的な機能をつかっていて「なるほど」という感じですね。