文档

§使用公共资源

在 Play 中提供公共资源与提供任何其他 HTTP 请求相同。它使用与使用控制器/操作路径将 CSS、JavaScript 或图像文件分发到客户端的常规资源相同的路由。

§public/ 文件夹

按照惯例,公共资源存储在应用程序的 public 文件夹中。您可以根据自己的喜好组织此文件夹。我们建议以下组织方式

public
 └ javascripts
 └ stylesheets
 └ images

如果您遵循此结构,入门将更简单,但一旦您了解其工作原理,就可以随意修改它。

§WebJars

WebJars 提供了一种方便且传统的打包机制,它是 sbt 的一部分。例如,您可以声明将使用流行的 Bootstrap 库,只需在您的构建文件中添加以下依赖项即可

libraryDependencies += "org.webjars" % "bootstrap" % "3.3.6"

为了方便起见,WebJars 会自动提取到相对于您的公共资源的 lib 文件夹中。例如,如果您声明了对 RequireJs 的依赖,那么您可以使用类似以下的代码行从视图中引用它

<script data-main="@routes.Assets.at("javascripts/main.js")" type="text/javascript" src="@routes.Assets.at("lib/requirejs/require.js")"></script>

请注意 lib/requirejs/require.js 路径。lib 文件夹表示提取的 WebJar 资源,requirejs 文件夹对应于 WebJar 的 artifactId,require.js 指的是 WebJar 根目录中的所需资源。为了说明,requirejs webjar 依赖项是在您的构建文件中声明的,如下所示

libraryDependencies += "org.webjars" % "requirejs" % "2.2.0"

§公共资产如何打包?

在构建过程中,public 文件夹的内容会被处理并添加到应用程序类路径中。

当您打包应用程序时,所有子项目的所有应用程序资产都会被聚合到一个名为 target/my-first-app-1.0.0-assets.jar 的单个 jar 文件中。此 jar 文件包含在分发包中,以便您的 Play 应用程序可以提供服务。此 jar 文件也可以用于将资产部署到 CDN 或反向代理。

§Assets 控制器

Play 带有一个内置控制器来提供公共资产。默认情况下,此控制器提供缓存、ETag、gzip 和压缩支持。Assets 控制器支持两种不同的样式:第一种是使用 Play 的配置,第二种是将资产路径直接传递给控制器。

§绑定 Assets 组件

如果您使用的是运行时依赖注入,Play 已经在 AssetsModule 中提供了绑定,该模块默认情况下会加载。(如果您没有使用资产,可以通过添加配置 play.modules.disabled += controllers.AssetsModule 来禁用此模块。)那里的绑定使 Assets 类可注入。

如果您使用组件特征来进行编译时依赖注入,则应该混合使用 controllers.AssetsComponents。然后控制器将作为 assets: Assets 可用。您不需要自己构造控制器。

§使用配置的资产

对于您只有一个地方集中存放资产的最常见情况,您可以使用配置来指定位置

play.assets {
  path = "/public"
  urlPrefix = "/assets"
}

并使用带有一个参数的 Assets.at 方法

Assets.at(file: String)

然后在路由中

GET  /assets/*file        controllers.Assets.at(file)

§直接传递资产路径

Assets 控制器还定义了一个带有两个参数的 at 操作

Assets.at(path: String, file: String)

path 参数必须固定,并定义由操作管理的目录。file 参数通常从请求路径中动态提取。

这是您在 conf/routes 文件中典型的 Assets 控制器映射

GET  /assets/*file        controllers.Assets.at(path="/public", file)

请注意,我们定义了 *file 动态部分,它将匹配 .* 正则表达式。例如,如果您向服务器发送此请求

GET /assets/javascripts/jquery.js

路由器将使用以下参数调用 Assets.at 操作

controllers.Assets.at("/public", "javascripts/jquery.js")

要路由到单个静态文件,路径和文件都必须指定

GET  /favicon.ico        controllers.Assets.at(path="/public", file="favicon.ico")

§公共资产的反向路由

对于在路由文件中映射的任何控制器,都会在 controllers.routes.Assets 中创建一个反向控制器。您可以使用它来反向获取公共资源所需的 URL。例如,从模板

<script src="@routes.Assets.at("javascripts/jquery.js")"></script>

DEV模式下,默认情况下会产生以下结果

<script src="/assets/javascripts/jquery.js"></script>

如果您的应用程序没有运行在DEV模式下 **并且** 存在jquery.min.jsjquery-min.js文件,那么默认情况下将使用压缩后的文件

<script src="/assets/javascripts/jquery.min.js"></script>

这使得在开发过程中更容易调试JavaScript文件。当然,这不仅适用于JavaScript文件,也适用于任何文件扩展名。
如果您不希望Play自动解析.min.*-min.*文件,无论您的应用程序运行在何种模式下,您可以在application.conf中设置play.assets.checkForMinified = false(或设置为true以始终解析最小文件,即使在DEV模式下)。

请注意,我们在反转路由时没有指定第一个folder参数。这是因为我们的路由文件为Assets.at操作定义了一个映射,其中folder参数是固定的。因此不需要指定它。

但是,如果您为Assets.at操作定义了两个映射,如下所示

GET  /javascripts/*file        controllers.Assets.at(path="/public/javascripts", file)
GET  /images/*file             controllers.Assets.at(path="/public/images", file)

那么您需要在使用反向路由器时指定这两个参数

<script src="@routes.Assets.at("/public/javascripts", "jquery.js")"></script>
<img src="@routes.Assets.at("/public/images", "logo.png")" />

§公共资产的反向路由和指纹

sbt-web将高度可配置的资产管道概念引入Play,例如在您的构建文件中

pipelineStages := Seq(rjs, digest, gzip)

以上将按顺序执行RequireJs优化器(sbt-rjs)、消化器(sbt-digest),然后是压缩(sbt-gzip)。与许多sbt任务不同,这些任务将按照声明的顺序依次执行。

本质上,资产指纹允许您的静态资产以积极的缓存指令提供给浏览器。这将为您的用户带来更好的体验,因为后续访问您的网站将导致需要下载的资产更少。Rails还描述了资产指纹的好处。

以上pipelineStages的声明以及您在plugins.sbt中对所需插件的必要addSbtPlugin声明是您的起点。然后,您必须向Play声明要进行版本控制的资产。

获取指纹资产的真实路径有两种方法。第一种方法使用静态状态,并支持与普通反向路由相同的样式。它通过查找运行中的 Play 应用程序设置的资产元数据来实现。第二种方法是使用配置并注入 AssetsFinder 来查找您的资产。

§使用反向路由和静态状态

如果您计划使用带有静态状态的反向路由器,以下路由文件条目声明所有资产都将被版本化

GET  /assets/*file  controllers.Assets.versioned(path="/public", file: Asset)

注意:确保您通过编写 file: Asset 来表明 file 是一个资产。

然后,您使用反向路由器,例如在 scala.html 视图中

<link rel="stylesheet" href="@routes.Assets.versioned("assets/css/app.css")">

这种方法的缺点是它需要特殊的逻辑来将您传入的路径中的 Asset 转换为带有摘要的最终最小化路径。它也更难进行单元测试,因为没有可以模拟的组件来定义路径。

§使用配置和 AssetsFinder

您也可以在配置中定义路径,并将 AssetsFinder 注入到您的控制器中以获取最终路径。在您的配置中设置资产 path(包含资产的目录)和 urlPrefix(应用程序中 URL 的前缀)

play.assets {
  path = "/public"
  urlPrefix = "/assets"
}

在您的路由文件中,您可以定义如下路由

GET  /assets/*file        controllers.Assets.versioned(file)

(您不应该在这里使用 : Asset 类型注释)

然后,您可以将 AssetsFinder 传递给您的模板,并使用它来获取最终路径

@(assetsFinder: AssetsFinder)

<link rel="stylesheet" href="@assetsFinder.path("assets/css/app.css")">

这种方法的优点是它不需要设置静态状态。这意味着您可以通过简单地传递 AssetsFinder 实例来对您的控制器和模板进行单元测试,而无需运行应用程序。这使得通过简单地实现返回 String 的抽象方法来模拟单元测试变得简单。

使用 AssetsFinder 方法还可以轻松地在同一个类加载器中同时运行多个自包含应用程序,因为它不使用任何静态状态。这对于测试也很有帮助。

AssetsFinder 接口在不使用指纹的情况下也能正常工作。如果找不到指纹和/或最小化资产,它将返回原始资产。

§Etag 支持

Assets 控制器自动管理 **ETag** HTTP 头。ETag 值由摘要生成(如果在资产管道中使用 sbt-digest),否则由资源名称和文件的最后修改日期生成。如果资源文件嵌入到文件中,则使用 JAR 文件的最后修改日期。

当 Web 浏览器发出包含此 **ETag** 的请求时,服务器可以响应 **304 NotModified**。

§Gzip 支持

如果找到具有相同名称但使用 .gz 后缀的资源,则 Assets 控制器也将提供后者,并添加以下 HTTP 头

Content-Encoding: gzip

在您的构建中包含 sbt-gzip 插件并在 pipelineStages 中声明其位置是生成 gzip 文件所需的全部操作。

§其他 Cache-Control 指令

使用 Etag 通常足以用于缓存目的。但是,如果您想为特定资源指定自定义 Cache-Control 标头,您可以在 application.conf 文件中指定它。例如

# Assets configuration
# ~~~~~
play.assets.cache."/public/stylesheets/bootstrap.min.css"="max-age=3600"

您还可以使用部分路径为该路径下所有资产指定自定义 Cache-Control,例如

# Assets configuration
# ~~~~~
play.assets.cache."/public/stylesheets/"="max-age=100"
play.assets.cache."/public/javascripts/"="max-age=200"

Play 将对 /public/javascripts 下的所有资产(如 /public/javascripts/main.js)使用 max-age=200,对 /public/stylesheets 下的所有资产(如 /public/stylesheets/main.css)使用 max-age=100

§如何应用其他指令

Play 按字典顺序对 Cache-Control 指令进行排序,并从更具体的到更不具体的排序。例如,给定以下配置

# Assets configuration
# ~~~~~
play.assets.cache."/public/stylesheets/"="max-age=101"
play.assets.cache."/public/stylesheets/layout/"="max-age=102"
play.assets.cache."/public/stylesheets/app/"="max-age=103"
play.assets.cache."/public/stylesheets/layout/main.css"="max-age=103"

play.assets.cache."/public/javascripts/"="max-age=201"
play.assets.cache."/public/javascripts/app/"="max-age=202"
play.assets.cache."/public/javascripts/app/main.js"="max-age=203"

指令将按以下顺序排序和应用

play.assets.cache."/public/javascripts/app/main.js"="max-age=203"
play.assets.cache."/public/javascripts/app/"="max-age=202"
play.assets.cache."/public/javascripts/"="max-age=201"

play.assets.cache."/public/stylesheets/app/"="max-age=103"
play.assets.cache."/public/stylesheets/layout/main.css"="max-age=103"
play.assets.cache."/public/stylesheets/layout/"="max-age=102"
play.assets.cache."/public/stylesheets/"="max-age=101"

注意:类似 play.assets.cache."/public/stylesheets"="max-age=101" 的配置将匹配 public/stylesheets.csspublic/stylesheets/main.css,因此您可能需要添加尾随 / 以更好地区分目录,例如 play.assets.cache."/public/stylesheets/"="max-age=101"

§托管资产

从 Play 2.3 开始,托管资产由 sbt-web 基于插件进行处理。在 2.3 之前,Play 将托管资产处理捆绑在一起,以 CoffeeScript、LESS、JavaScript 规范(ClosureCompiler)和 RequireJS 优化形式提供。以下部分描述了 sbt-web 以及如何实现等效的 2.2 功能。但请注意,Play 不限于此资产处理技术,因为随着时间的推移,许多插件应该会变得可用于 sbt-web。请查看 sbt-web 项目以了解有关可用插件的更多信息。

许多插件使用 sbt-web 的 js-engine 插件。js-engine 能够通过优秀的 Trireme 项目在 JVM 中执行使用 Node API 编写的插件,或者直接在 Node.js 上执行以获得更高的性能。请注意,这些工具仅在开发周期中使用,在 Play 应用程序的运行时执行期间不会参与。如果您安装了 Node.js,则建议您声明以下环境变量。对于 Unix,如果 SBT_OPTS 已在其他地方定义,则可以

export SBT_OPTS="$SBT_OPTS -Dsbt.jse.engineType=Node"

上述声明确保在执行任何 sbt-web 插件时使用 Node.js。

§范围请求支持

Assets 控制器自动支持 RFC 7233 的一部分,该规范定义了范围请求和部分响应的工作方式。如果请求中存在可满足的 Range 标头,Assets 控制器将提供 206 Partial Content。它还将为所有资产交付返回 Accept-Ranges: bytes

注意:除了进行一些解析以更好地处理多个范围之外,multipart/byteranges 尚未完全支持。

您也可以在不使用 Assets 控制器的情况下,在交付文件时返回 206 Partial Content

§Scala 版本

def video(videoId: Long): Action[AnyContent] = Action { implicit request =>
  val videoFile = getVideoFile(videoId)
  RangeResult.ofFile(videoFile, request.headers.get(RANGE), Some("video/mp4"))
}

§Java 版本

public Result video(Http.Request request, Long videoId) {
  File videoFile = getVideoFile(videoId);
  return RangeResults.ofFile(request, videoFile);
}

这两个示例都将根据请求的范围交付视频文件的一部分。

下一步:使用 CoffeeScript


发现此文档中的错误?此页面的源代码可以在 此处 找到。在阅读 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区开始对话。