从Play Framework 2 Scala控制器提供静态文件的首选方法是什么?
该文件与我的应用程序捆绑在一起,因此无法对文件系统绝对/path/to/the/file
进行硬编码,因为它的位置取决于 Play 应用程序的安装位置。
该文件放置在公共/目录中,但不放在应用程序/资产/中,因为我不希望Play编译它。
(我不简单地向该文件添加路由的原因是,在访问该文件之前需要登录,否则毫无用处。
这是我到目前为止所做的,但这在我的生产服务器上中断了。
object Application ...
def viewAdminPage = Action ... {
... authorization ...
val adminPageFile = Play.getFile("/public/admin/index.html")
Ok.sendFile(adminPageFile, inline = true)
}
在我的routes
文件中,我有这一行:
GET /-/admin/ controllers.Application.viewAdminPage
问题是在我的生产服务器上,会发生此错误:
FileNotFoundException: app1/public/admin/index.html
是否有其他方法,而不是 Play.getFile
和 OK.sendFile
,来指定要提供的文件?这在生产中永远不会中断?
(我的应用程序安装在/some-dir/app1/
中,我从/some-dir/
启动它(没有app1/
)——如果我从/some-dir/app1/
启动应用程序,也许一切都会正常工作。但是我想知道一个人"应该"怎么做,从控制器内部提供静态文件?这样一切总是在生产服务器上也能正常工作,无论我碰巧从哪里启动应用程序)
检查流式 HTTP 响应文档
def index = Action {
Ok.sendFile(
content = new java.io.File("/tmp/fileToServe.pdf"),
fileName = _ => "termsOfService.pdf"
)
}
您可以向fileName
添加一些随机字符串(每个登录用户单独),以避免在经过身份验证的用户和非身份验证用户之间共享下载链接,还可以进行高级下载统计信息。
我这样做了:(但请参阅下面的更新!
val fileUrl: java.net.URL = this.getClass().getResource("/public/admin/file.html")
val file = new java.io.File(adminPageUrl.toURI())
Ok.sendFile(file, inline = true)
(this
是控制器,它与正在提供的文件位于(并且必须)位于同一包中。
这是一个相关的问题:在java中使用相对路径打开资源
更新
通过 URI 访问文件会导致错误:IllegalArgumentException: URI is not hierarchical
,如果文件随后位于 JAR 内,则运行如下: play stage
然后target/start
.
因此,我将文件作为流读取,将其转换为字符串,并将该字符串作为HTML发送:
val adminPageFileString: String = {
// In prod builds, the file is embedded in a JAR, and accessing it via
// an URI causes an IllegalArgumentException: "URI is not hierarchical".
// So use a stream instead.
val adminPageStream: java.io.InputStream =
this.getClass().getResourceAsStream("/public/admin/index.html")
io.Source.fromInputStream(adminPageStream).mkString("")
}
...
return Ok(adminPageFileString) as HTML
Play有一个内置的方法:
Ok.sendResource("public/admin/file.html", classLoader)
您可以从注入的Environment
environment.classLoader
或this.getClass.getClassLoader
获取类装入器。
手动方法如下:
val url = Play.resource(file)
url.map { url =>
val stream = url.openStream()
val length = stream.available
val resourceData = Enumerator.fromStream(stream)
val headers = Map(
CONTENT_LENGTH -> length.toString,
CONTENT_TYPE -> MimeTypes.forFileName(file).getOrElse(BINARY),
CONTENT_DISPOSITION -> s"""attachment; filename="$name"""")
SimpleResult(
header = ResponseHeader(OK, headers),
body = resourceData)
使用资产控制器的等效项是这样的:
val name = "someName.ext"
val response = Assets.at("/public", name)(request)
response
.withHeaders(CONTENT_DISPOSITION -> s"""attachment; filename="$name"""")
另一个变体,不使用字符串,但通过流式传输文件内容:
def myStaticRessource() = Action { implicit request =>
val contentStream = this.getClass.getResourceAsStream("/public/content.html")
Ok.chunked(Enumerator.fromStream(contentStream)).as(HTML)
}