文件

§Play 2.6 遷移指南

這是從 Play 2.5 遷移到 Play 2.6 的指南。如果您需要從 Play 的早期版本遷移,則必須先按照 Play 2.5 遷移指南 進行操作。

§如何遷移

在 sbt 中載入/執行 Play 專案之前,需要執行以下步驟來更新您的 sbt 建置。

§Play 升級

更新 project/plugins.sbt 中的 Play 版本號碼以升級 Play

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.x")

其中 2.6.x 中的「x」是您要使用的 Play 的次要版本,例如 2.6.0

§sbt 升級至 0.13.15

Play 2.6 需要升級到至少 sbt 0.13.15。sbt 的 0.13.15 版本有許多 改進和錯誤修正(另請參閱 sbt 0.13.13 的變更)。

sbt 1.x 從 Play 2.6.6 開始支援。如果您使用其他 sbt 外掛程式,您可能需要檢查是否有與 sbt 1.x 相容的較新版本

若要更新,請變更您的 project/build.properties,使其讀取

sbt.version=0.13.15

§Guice DI 支援移至獨立模組

在 Play 2.6 中,核心 Play 模組不再包含 Guice。您需要透過將 guice 新增至您的 libraryDependencies 來設定 Guice 模組

libraryDependencies += guice

§OpenID 支援移至獨立模組

在 Play 2.6 中,核心 Play 模組不再包含 play.api.libs.openid(Scala)和 play.libs.openid(Java)中的 OpenID 支援。若要使用這些套件,請將 openId 新增至您的 libraryDependencies

libraryDependencies += openId

§Play JSON 移至獨立專案

Play JSON 已移至 https://github.com/playframework/play-json 中的獨立程式庫。由於 Play JSON 沒有依賴於 Play 的其他部分,因此主要變更為 PlayImport 中的 json 值將不再在您的 sbt 建置中運作。您必須手動指定程式庫

libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.0"

此外,play-json 有別於核心 Play 程式庫的獨立版本週期,因此版本不再與 Play 版本同步。

§Play Iteratees 移至獨立專案

Play Iteratees 已移至獨立程式庫,網址為 https://github.com/playframework/play-iteratees。由於 Play Iteratees 沒有依賴 Play 的其他部分,因此主要變更為您必須手動指定程式庫

libraryDependencies += "com.typesafe.play" %% "play-iteratees" % "2.6.1"

該專案還有一個子專案,可將 Iteratees 與 Reactive Streams 整合。您可能還需要新增下列依賴項

libraryDependencies += "com.typesafe.play" %% "play-iteratees-reactive-streams" % "2.6.1"

注意:輔助類別 play.api.libs.streams.Streams 已移至 play-iteratees-reactive-streams,現在稱為 play.api.libs.iteratee.streams.IterateeStreams。因此您可能需要新增 Iteratees 依賴項,並在必要時使用新類別。

最後,Play Iteratees 有獨立的版本編號,因此版本不再與 Play 版本同步。

§Akka HTTP 作為預設伺服器引擎

Play 現在使用 Akka-HTTP 伺服器引擎作為預設後端。如果您需要出於某種原因將其變回 Netty(例如,如果您使用 Netty 的 原生傳輸),請參閱 Netty Server 文件中的操作方式。

您可以在 Akka HTTP Server 後端 中閱讀更多資訊。

§Akka HTTP 伺服器逾時

Play 2.5.x 沒有 Netty Server 的要求逾時設定,它是預設伺服器後端。但 Akka HTTP 有閒置連線和要求的逾時(請參閱 Akka HTTP 設定 文件中的更多詳細資訊)。Akka HTTP 文件 指出

Akka HTTP 附帶各種內建逾時機制,以保護您的伺服器免於惡意攻擊或程式設計錯誤。

而您可以 在此處 看到 akka.http.server.idle-timeoutakka.http.server.request-timeoutakka.http.server.bind-timeout 的預設值。Play 有 它自己的組態來定義逾時,因此如果您開始看到許多 503 服務無法使用,您可以將組態變更為對您的應用程式更合理的數值,例如

play.server.http.idleTimeout = 60s
play.server.akka.requestTimeout = 40s

§Scala 模式變更

Scala 模式 已從列舉重構為案例物件的階層。由於此重構,大部分的 Scala 程式碼不會變更。但是,如果您在 Java 程式碼中存取 Scala 模式值,您需要將它從

// Consider this Java code
play.api.Mode scalaMode = play.api.Mode.Test();

改寫為

// Consider this Java code
play.api.Mode scalaMode = play.Mode.TEST.asScala();

在 Java 和 Scala 模式之間轉換也變得更容易

// In your Java code
play.api.Mode scalaMode = play.Mode.DEV.asScala();

或在您的 Scala 程式碼中

play.Mode javaMode = play.api.Mode.Dev.asJava

此外,play.api.Mode.Mode 現在已過時,您應該改用 play.api.Mode

§Writeable[JsValue] 變更

先前,預設的 Scala Writeable[JsValue] 允許您定義一個隱含的 編碼器,它將允許您使用不同的字元集來寫入。這可能會造成問題,因為 application/json 並不像基於文字的內容類型。它只允許 Unicode 字元集(UTF-8UTF-16UTF-32),並且不定義像許多基於文字的內容類型那樣的 charset 參數。

現在,預設的 Writeable[JsValue] 不採用任何隱含參數,並且總是寫入 UTF-8。這涵蓋了大部分情況,因為大多數使用者都希望對 JSON 使用 UTF-8。它也允許我們輕鬆使用更有效率的內建方法將 JSON 寫入位元組陣列。

如果您需要舊有的行為,您可以使用 play.api.http.Writeable.writeableOf_JsValue(codec, contentType) 為您想要的編碼器和內容類型定義一個具有任意編碼器的 Writeable

§Scala Controller 變更

慣用的 Play controller 過去需要全域狀態。最需要的地方是在全域 play.api.mvc.Action 物件和 BodyParsers#parse 方法中。

我們提供了幾個新的 controller 類別,提供注入該狀態的新方法,提供相同的語法
- BaseController:一個具有抽象 ControllerComponents 的特質,可以由一個實作類別提供。
- AbstractController:一個抽象類別,擴充 BaseController,並具有 ControllerComponents 建構函式參數,可以使用建構函式注入來注入。
- InjectedController:一個特質,擴充 BaseController,透過方法注入(呼叫 setControllerComponents 方法)取得 ControllerComponents。如果您使用 Guice 等執行時期 DI 架構,這會自動完成。

ControllerComponents 僅用於將通常在 controller 中使用的元件組合在一起。您也可以透過擴充 ControllerHelpers 並注入您自己的元件組合,為您的應用程式建立您自己的基本 controller。Play 不需要您的 controller 實作任何特定特質。

請注意,BaseController 會讓 Actionparse 參照注入的執行個體,而不是全域物件,這通常是您想要執行的動作。

以下是使用 AbstractController 的範例程式碼

class FooController @Inject() (components: ControllerComponents)
    extends AbstractController(components) {

  // Action and parse now use the injected components
  def foo = Action(parse.json) {
    Ok
  }
}

以及使用 BaseController

class FooController @Inject() (val controllerComponents: ControllerComponents) extends BaseController {

  // Action and parse now use the injected components
  def foo = Action(parse.json) {
    Ok
  }
}

InjectedController

class FooController @Inject() () extends InjectedController {

  // Action and parse now use the injected components
  def foo = Action(parse.json) {
    Ok
  }
}

InjectedController 會透過呼叫 setControllerComponents 方法取得其 ControllerComponents,而此方法會由符合 JSR-330 的相依性注入自動呼叫。我們不建議將 InjectedController 與編譯時期注入搭配使用。如果您計畫大量手動單元測試控制器,我們也建議避免使用 InjectedController,因為它會隱藏相依性。

如果您偏好手動傳遞個別相依性,您可以執行此動作,並擴充 ControllerHelpers,它沒有相依性或狀態。以下是範例

class Controller @Inject() (
    action: DefaultActionBuilder,
    parse: PlayBodyParsers,
    messagesApi: MessagesApi
  ) extends ControllerHelpers {
  def index = action(parse.text) { request =>
    Ok(messagesApi.preferred(request)("hello.world"))
  }
}

§Scala ActionBuilder 和 BodyParser 變更

Scala ActionBuilder 特質已修改為將主體類型指定為類型參數,並新增一個抽象的 parser 成員作為預設主體剖析器。您需要修改 ActionBuilder,並直接傳遞主體剖析器。

play.api.mvc.Action 全域物件和 BodyParsers#parse 現在已標示為不建議使用。它們已由可注入的特質 DefaultActionBuilderPlayBodyParsers 分別取代。如果您在控制器內,它們會由新的 BaseController 特質自動提供 (請參閱上述 控制器變更)。

§Cookie

對於 Java 使用者,我們現在建議使用 play.mvc.Http.Cookie.builder 來建立新的 Cookie,例如

Http.Cookie cookie = Cookie.builder("color", "blue")
  .withMaxAge(3600)
  .withSecure(true)
  .withHttpOnly(true)
  .withSameSite(SameSite.STRICT)
  .build();

這比一般的建構函式呼叫更易於閱讀,而且如果我們在未來新增/移除 Cookie 屬性,它將會與原始程式碼相容。

§SameSite 屬性,已啟用 session 和 flash

Cookie 現在可以有額外的 SameSite 屬性,可用於防止 CSRF。有三個可能的狀態

此外,我們已將 session 和 flash Cookie 移至預設使用 SameSite=Lax。您可以使用設定來調整這項設定。例如

play.http.session.sameSite = null // no same-site for session
play.http.flash.sameSite = "strict" // strict same-site for flash

注意:此功能目前 不受許多瀏覽器支援,因此您不應依賴它。目前只有 Chrome 和 Opera 這兩個主要瀏覽器支援 SameSite。

§__Host 和 __Secure 前置詞

我們也已新增支援 __Host 和 __Secure Cookie 名稱前置詞

如果您碰巧將這些前綴用於 Cookie 名稱,這只會影響您。如果您有,在序列化和反序列化這些 Cookie 時,如果未設定適當的屬性,Play 會發出警告,然後自動為您設定。若要移除警告,請停止對 Cookie 使用這些前綴,或務必將屬性設定如下

§資產

§使用編譯時期 DI 繫結資產

如果您正在使用編譯時期 DI,您應混合 controllers.AssetsComponents 並使用它來取得 assets: Assets 控制器實例

class MyComponents(context: Context) extends BuiltInComponentsFromContext(context) with AssetsComponents {
  lazy val router = new Routes(httpErrorHandler, assets)
}

如果您有現有的 lazy val assets: Assets,您可以將它移除。

§資產設定

現有的使用者介面 API 沒有變更,但我們建議轉移到 AssetsFinder API 以尋找資產和在設定中設定資產目錄

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

然後您可以在路由中執行

# prefix must match `play.assets.urlPrefix`
GET /assets/*file           controllers.Assets.at(file)
GET /versionedAssets/*file  controllers.Assets.versioned(file)

您不再需要在引數清單的開頭提供資產路徑,因為現在會從設定中讀取。

然後在您的範本中,您可以使用 AssetsFinder#path 來尋找資產的最終路徑

@(assets: AssetsFinder)

<img alt="hamburger" src="@assets.path("images/hamburger.jpg")">

您仍可以使用反向路由搭配 Assets.versioned,但需要一些全域狀態才能將您提供的資產名稱轉換為最終資產名稱,如果您想要一次執行多個應用程式,這可能會造成問題。

§表單變更

從 Play 2.6 開始,查詢字串參數在使用 bindFromRequest() 搭配 POSTPUTPATCH 要求時,將不再繫結到表單實例。

在這個版本中,已在 2.5 中標示為不建議使用的靜態方法(例如 DynamicForm.form())已被移除。如果您仍然使用這些方法,請參閱 Play 2.5 遷移指南 以取得有關如何遷移的詳細資訊。

§Java 表單變更

現在,errors() 方法已標示為 play.data.Form 執行個體的不建議使用。您現在應該使用 allErrors(),它會傳回一個簡單的 List<ValidationError>,而不是 Map<String,List<ValidationError>>。在 Play 2.6 之前,您呼叫 .errors().get("key"),現在您可以直接呼叫 .errors("key")

從現在開始,在表單類別中實作的 validate 方法(通常用於跨欄位驗證)是類別層級約束的一部分。請查看 進階驗證 文件,以取得有關如何使用此類約束的更多資訊。
現有的 validate 方法可以輕鬆地透過使用 @Validate 標記受影響的表單類別,以及根據驗證方法的傳回類型,透過實作 Validatable 介面和適用的類型引數(全部定義在 play.data.validation.Constraints)來進行遷移。

傳回類型 要實作的介面
字串 Validatable<String>
ValidationError Validatable<ValidationError>
List<ValidationError> Validatable<List<ValidationError>>
Map<String,List<ValidationError>>
(不再支援;請改用 List
Validatable<List<ValidationError>>

例如,現有的表單如下所示

public class MyForm {
    //...
    public String validate() {
        //...
    }
}

必須變更為

import play.data.validation.Constraints.Validate;
import play.data.validation.Constraints.Validatable;

@Validate
public class MyForm implements Validatable<String> {
    //...
    @Override
    public String validate() {
        //...
    }
}

請注意:「舊的」validate 方法只會在所有其他約束都成功後才會呼叫。然而,預設情況下,類別層級約束會與任何其他約束註解同時呼叫,無論它們是否通過或失敗。若要(也)定義約束之間的順序,您現在可以使用 約束群組

§JPA 遷移注意事項

請參閱 JPA 遷移注意事項

§I18n 遷移注意事項

請參閱 I18N API 遷移

§快取 API 遷移注意事項

請參閱 快取 API 遷移

§Java 組態 API 遷移注意事項

請參閱 Java 組態遷移

§Scala 組態 API

Scala play.api.Configuration API 現在有新的方法,允許使用 ConfigLoader 載入任何類型。這些新方法預期組態檔案中存在組態金鑰。例如,下列舊程式碼

val myConfig: String = configuration.getString("my.config.key").getOrElse("default")

應變更為

val myConfig: String = configuration.get[String]("my.config.key")

且值「default」應在組態中設定為 my.config.key = default

或者,如果程式碼中需要自訂邏輯來取得預設值,您可以在組態檔案中將預設值設定為 null (my.config.key = null),並讀取 Option[T]

val myConfigOption: Option[String] = configuration.get[Option[String]]("my.config.key")
val myConfig: String = myConfigOption.getOrElse(computeDefaultValue())

此外,舊的 play.api.Configuration 中有幾個方法會傳回 Java 類型,例如 getBooleanList。如果可能,我們建議改用 Scala 版本 get[Seq[Boolean]]。如果無法使用,您可以存取 underlying Config 物件,並從中呼叫 getBooleanList

現有方法上的不建議使用訊息也會說明如何遷移每個方法。請參閱 Scala 組態文件,以取得關於 play.api.Configuration 正確用法的更多詳細資料。

§Play JSON API 變更

§JSON 陣列索引查詢

如果您使用 Scala play-json API,則 JsLookup 隱式類別的工作方式有微小的變更。例如,如果您有類似下列的程式碼

val bar = (jsarray(index) \ "bar").as[Bar]

其中 index 是陣列索引,而 jsarrayJsArray,現在您應該撰寫

val bar = (jsarray \ index \ "bar").as[Bar]

這是為了讓 JsArray 上的索引行為與 Scala 中其他集合的行為一致。現在 jsarray(index) 方法會傳回索引處的值,如果不存在則會擲回例外狀況。

此外,play-json API 有微小的變更,play.api.data.validation.ValidationError 已變更為 play.api.libs.json.JsonValidationError。例如,如果您有類似下列的程式碼

ValidationError("Validation Error")

其中「驗證錯誤」是訊息,現在您應該撰寫

JsonValidationError("Validation Error")

§移除的 API

§移除的 Crypto API

Crypto API 已移除已棄用的類別 play.api.libs.Cryptoplay.libs.CryptoAESCTRCrypterCrypto 中的 CSRF 參照已被 CSRFTokenSigner 取代。Crypto 中的 session cookie 參照已被 CookieSigner 取代。請參閱 CryptoMigration25 以取得更多資訊。

§移除的 Akka 已棄用方法

已移除已棄用的靜態方法 play.libs.Akka.systemplay.api.libs.concurrent.Akka.system。使用相依性注入來取得 ActorSystem 的執行個體,並存取 actor 系統。

對於 Scala

class MyComponent @Inject() (system: ActorSystem) {

}

對於 Java

public class MyComponent {

    private final ActorSystem system;

    @Inject
    public MyComponent(ActorSystem system) {
        this.system = system;
    }
}

此外,Play 2.6.x 現在使用 Akka 2.5.x 發行系列。閱讀 Akka 從 2.4.x 到 2.5.x 的移轉指南,以了解如何視需要調整您自己的程式碼。

§移除的 Yaml API

我們移除 play.libs.Yaml,因為在 play 中不再使用它。如果您仍需要支援 Play YAML 整合,您需要在 build.sbt 中新增 snakeyaml

libraryDependencies += "org.yaml" % "snakeyaml" % "1.17"

並在您的程式碼中建立下列 Wrapper

public class Yaml {

    private final play.Environment environment;

    @Inject
    public Yaml(play.Environment environment) {
        this.environment = environment;
    }

    /**
     * Load a Yaml file from the classpath.
     */
    public Object load(String resourceName) {
        return load(
            environment.resourceAsStream(resourceName),
            environment.classLoader()
        );
    }

    /**
     * Load the specified InputStream as Yaml.
     *
     * @param classloader The classloader to use to instantiate Java objects.
     */
    public Object load(InputStream is, ClassLoader classloader) {
        org.yaml.snakeyaml.Yaml yaml = new org.yaml.snakeyaml.Yaml(new CustomClassLoaderConstructor(classloader));
        return yaml.load(is);
    }

}

或在 Scala 中

class Yaml @Inject()(environment: play.api.Environment) {
  def load(resourceName: String) = {
    load(environment.resourceAsStream(resourceName), environment.classLoader)
  }

  def load(inputStream: InputStream, classLoader: ClassLoader) = {
    new org.yaml.snakeyaml.Yaml(new CustomClassLoaderConstructor(classloader)).load(inputStream)
  }
}

如果您明確地依賴 Play 的替代 DI 函式庫,或已定義您自己的自訂應用程式載入程式,則不需進行任何變更。

提供 Play DI 支援的函式庫應定義 play.application.loader 組態金鑰。如果未提供外部 DI 函式庫,除非您將其指向 ApplicationLoader,否則 Play 將拒絕啟動。

§已移除已棄用的 play.Routes

用於建立 JavaScript 路由器的已棄用 play.Routes 類別已移除。您現在必須使用新的 Java 或 Scala 輔助程式

§已移除函式庫

為了讓預設的 Play 發行版小一點,我們已移除一些函式庫。下列函式庫不再是 Play 2.6 中的相依性,因此如果您使用它們,您需要手動將它們新增到您的建置中。

§已移除 Joda-Time

我們建議使用 java.time API,因此我們從 Play 的核心移除 joda-time 支援。

Play 的 Scala 表單函式庫有一些 Joda 格式。如果您不希望移轉,您可以在您的 build.sbt 中新增 jodaForms 模組

libraryDependencies += jodaForms

然後匯入對應的物件

import play.api.data.JodaForms._

如果您需要在 play-json 中支援 Joda,您可以新增下列相依性

libraryDependencies += "com.typesafe.play" %% "play-json-joda" % playJsonVersion

其中 playJsonVersion 是您想要使用的 play-json 版本。Play 2.6.x 應與 play-json 2.6.x 相容。請注意,play-json 現在是一個獨立的專案 (稍後說明)。

import play.api.libs.json.JodaWrites._
import play.api.libs.json.JodaReads._

§已移除 Joda-Convert

Play 在內部有一些 joda-convert 用途,如果您在您的專案中使用它,您需要將它新增到您的 build.sbt

libraryDependencies += "org.joda" % "joda-convert" % "1.8.1"

§已移除 XercesImpl

Play 使用 Xerces XML 函式庫來處理 XML。由於現代 JVM 使用 Xerces 作為參考實作,我們已將其移除。如果您的專案依賴外部套件,您只要將其新增到 build.sbt 即可

libraryDependencies += "xerces" % "xercesImpl" % "2.11.0"

§H2 移除

Play 的先前版本已預先封裝 H2 資料庫。但為了縮小 Play 的核心,我們已將其移除。如果您使用 H2,您可以將其新增到 build.sbt

libraryDependencies += "com.h2database" % "h2" % "1.4.193"

如果您只在測試中使用它,您也可以只使用 Test 範圍

libraryDependencies += "com.h2database" % "h2" % "1.4.193" % Test

在您新增相依項後,H2 瀏覽器 仍會運作。

§snakeyaml 移除

Play 移除了 play.libs.Yaml,因此對 snakeyaml 的相依項已取消。如果您仍使用它,請將其新增到 build.sbt

libraryDependencies += "org.yaml" % "snakeyaml" % "1.17"

另請參閱 Yaml API 移除的注意事項

§Tomcat-servlet-api 移除

Play 移除了 tomcat-servlet-api,因為它沒有用處。如果您仍使用它,請將其新增到 build.sbt

libraryDependencies += "org.apache.tomcat" % "tomcat-servlet-api" % "8.0.33"

§fork-run 移除

sbt-fork-run-plugin 將不再產生,因為它僅適用於現已結束生命週期的 activator 實用程式。由於它不再適用於 2.6,因此可以安全地將其完全移除。

§要求屬性

所有要求物件現在都包含屬性。要求屬性是要求標籤的替代品。標籤現在已不建議使用,您應該升級到屬性。屬性比標籤更強大;您可以使用屬性在要求中儲存物件,而標籤僅支援儲存字串。

§要求標籤不建議使用

標籤已棄用,因此您應開始從使用標籤遷移到使用屬性。遷移應相當簡單。

最簡單的遷移路徑是從標籤遷移到具有 String 類型的屬性。

Java 之前

// Getting a tag from a Request or RequestHeader
String userName = req.tags().get("userName");
// Setting a tag on a Request or RequestHeader
req.tags().put("userName", newName);
// Setting a tag with a RequestBuilder
Request builtReq = requestBuilder.tag("userName", newName).build();

Java 之後

class Attrs {
  public static final TypedKey<String> USER_NAME = TypedKey.<String>create("userName");
}

// Getting an attribute from a Request or RequestHeader
String userName = req.attrs().get(Attrs.USER_NAME);
String userName = req.attrs().getOptional(Attrs.USER_NAME);
// Setting an attribute on a Request or RequestHeader
Request newReq = req.withTags(req.tags().put(Attrs.USER_NAME, newName));
// Setting an attribute with a RequestBuilder
Request builtReq = requestBuilder.attr(Attrs.USER_NAME, newName).build();

Scala 之前

// Getting a tag from a Request or RequestHeader
val userName: String = req.tags("userName")
val optUserName: Option[String] = req.tags.get("userName")
// Setting a tag on a Request or RequestHeader
val newReq = req.copy(tags = req.tags.updated("userName", newName))

Scala 之後

object Attrs {
  val UserName: TypedKey[String] = TypedKey("userName")
}
// Getting an attribute from a Request or RequestHeader
val userName: String = req.attrs(Attrs.UserName)
val optUserName: [String] = req.attrs.get(Attrs.UserName)
// Setting an attribute on a Request or RequestHeader
val newReq = req.addAttr(Attrs.UserName, newName)

但是,如果適當,我們建議您將 String 標籤轉換為具有非 String 值的屬性。將標籤轉換為非 String 物件有幾個好處。首先,您將使您的程式碼更具型別安全性。這將提高您程式碼的可靠性,並使其更容易理解。其次,您儲存在屬性中的物件可以包含多個屬性,讓您可以將多個標籤彙總成單一值。第三,將標籤轉換為屬性表示您不需要對來自 String 的值進行編碼和解碼,這可能會提高效能。

class Attrs {
  public static final TypedKey<User> USER = TypedKey.<User>create("user");
}

Scala 之後

object Attrs {
  val UserName: TypedKey[User] = TypedKey("user")
}

§呼叫 FakeRequest.withCookies 不再更新 Cookies 標頭

要求 cookie 現在儲存在要求屬性中。先前它們儲存在要求的 Cookie 標頭 String 中。這需要在 cookie 變更時對 cookie 進行編碼和解碼到標頭。

現在 cookie 儲存在要求屬性中,更新 cookie 將變更新的 cookie 屬性,但不會變更 Cookie HTTP 標頭。如果您依賴呼叫 withCookies 會更新標頭的事實,這只會影響您的測試。

如果您仍然需要舊行為,您仍然可以使用 Cookies.encodeCookieHeaderCookie 物件轉換為 HTTP 標頭,然後使用 FakeRequest.withHeaders 儲存標頭。

§play.api.mvc.Security.username (Scala API)、session.username 變更

play.api.mvc.Security.username (Scala API)、session.username 設定檔金鑰和依賴動作輔助程式已棄用。Security.username 只會從設定檔中擷取 session.username 金鑰,其中定義了用於取得使用者名稱的階段金鑰。已移除它,因為它需要靜態才能運作,而且自己實作相同或類似的行為相當容易。

您可以使用 configuration.get[String]("session.username") 從設定檔中自己讀取使用者名稱階段金鑰。

如果您使用 Authenticated(String => EssentialAction) 方法,您可以輕鬆建立自己的動作來執行類似的事情

def AuthenticatedWithUsername(action: String => EssentialAction) =
  WithAuthentication[String](_.session.get(UsernameKey))(action)

其中 UsernameKey 代表您想用於使用者名稱的階段金鑰。

§Request Security (Java API) username 屬性現在是屬性

Java Request 物件包含一個 username 屬性,當 Security.Authenticated 註解新增到 Java 動作時,會設定此屬性。在 Play 2.6 中,username 屬性已棄用。username 屬性方法已更新,以將使用者名稱儲存在 Security.USERNAME 屬性中。您應該更新您的程式碼,以直接使用 Security.USERNAME 屬性。在 Play 的未來版本中,我們將移除 username 屬性。

進行此變更的原因是,username 屬性是 Security.Authenticated 註解的特殊情況。現在我們有了屬性,就不再需要特殊情況了。

現有的 Java 程式碼

// Set the username
Request reqWithUsername = req.withUsername("admin");
// Get the username
String username = req1.username();
// Set the username with a builder
Request reqWithUsername = new RequestBuilder().username("admin").build();

已更新的 Java 程式碼

import play.mvc.Security.USERNAME;

// Set the username
Request reqWithUsername = req.withAttr(USERNAME, "admin");
// Get the username
String username = req1.attr(USERNAME);
// Set the username with a builder
Request reqWithUsername = new RequestBuilder().putAttr(USERNAME, "admin").build();

§路由器標籤現在是屬性

如果您使用任何 Router.Tags.* 標籤,您應該變更您的程式碼以使用新的 Router.Attrs.HandlerDef(Scala)或 Router.Attrs.HANDLER_DEF(Java)屬性。現有的標籤仍然可用,但已標示為不建議使用,且將在 Play 的未來版本中移除。

此新屬性包含一個 HandlerDef 物件,其中包含標籤中目前的所有資訊。目前的標籤全部對應到 HandlerDef 物件中的欄位

Java 標籤名稱 Scala 標籤名稱 HandlerDef 方法
ROUTE_PATTERN RoutePattern path
ROUTE_VERB RouteVerb verb
ROUTE_CONTROLLER RouteController controller
ROUTE_ACTION_METHOD RouteActionMethod method
ROUTE_COMMENTS RouteComments comments

注意:作為此變更的一部分,HandlerDef 物件已從 play.core.routing 內部套件移至 play.api.routing 公開 API 套件。

§play.api.libs.concurrent.Execution 已標示為不建議使用

play.api.libs.concurrent.Execution 類別已標示為不建議使用,因為它在後端使用全域可變狀態來提取「目前的」應用程式的 ExecutionContext。

如果您想要指定您先前擁有的隱式行為,那麼您應該使用 依賴注入 在建構函式中隱式傳入執行緒環境

class MyController @Inject()(implicit ec: ExecutionContext) {

}

或如果您使用 編譯時期依賴注入,則從 BuiltInComponents 傳入

class MyComponentsFromContext(context: ApplicationLoader.Context)
  extends BuiltInComponentsFromContext(context) {
  val myComponent: MyComponent = new MyComponent(executionContext)
}

然而,即使在一般情況下,也有一些好的理由說明您可能不想要匯入執行緒內容。在一般情況下,應用程式的執行緒內容適用於呈現動作,並執行不涉及封鎖 API 呼叫或 I/O 活動的 CPU 相關活動。如果您正在呼叫資料庫,或進行網路呼叫,則您可能想要定義您自己的自訂執行緒內容。

建立自訂執行緒內容的建議方式是透過 CustomExecutionContext,它使用 Akka 調度系統 (java / scala),以便可透過組態定義執行器。

若要使用您自己的執行緒內容,請使用 application.conf 檔案中調度器的完整路徑來延伸 CustomExecutionContext 抽象類別

import play.api.libs.concurrent.CustomExecutionContext

class MyExecutionContext @Inject()(actorSystem: ActorSystem)
 extends CustomExecutionContext(actorSystem, "my.dispatcher.name")
import play.libs.concurrent.CustomExecutionContext;
class MyExecutionContext extends CustomExecutionContext {
   @Inject
   public MyExecutionContext(ActorSystem actorSystem) {
     super(actorSystem, "my.dispatcher.name");
   }
}

然後適當地注入您的自訂執行緒內容

class MyBlockingRepository @Inject()(implicit myExecutionContext: MyExecutionContext) {
   // do things with custom execution context
}

請參閱 ThreadPools 頁面以取得有關自訂執行緒池組態的更多資訊,以及 JavaAsync / ScalaAsync 以使用 CustomExecutionContext

§變更 play.api.test Helpers

以下已棄用的測試輔助程式已在 2.6.x 中移除

§Java API

§變更範本輔助函式

requireJs 範本輔助函式在 views/helper/requireJs.scala.html 中使用 Play.maybeApplication 存取組態。

requireJs 範本輔助函式新增一個額外的參數 isProd,用來表示是否應使用輔助函式的縮小版本

@requireJs(core = routes.Assets.at("javascripts/require.js").url, module = routes.Assets.at("javascripts/main").url, isProd = true)

§變更檔案副檔名至 MIME 類型的對應

檔案副檔名至 MIME 類型的對應已移至 reference.conf,因此完全透過組態涵蓋,在 play.http.fileMimeTypes 設定中。先前清單是硬編碼在 play.api.libs.MimeTypes 中。

請注意,play.http.fileMimeTypes 組態設定使用三個引號定義為單一字串,這是因為多個檔案副檔名有中斷 HOCON 的語法,例如 c++

若要附加自訂 MIME 類型,請使用 HOCON 字串值串接

play.http.fileMimeTypes = ${play.http.fileMimeTypes} """
  foo=text/bar
"""

有一個語法允許將組態定義為 mimetype.foo=text/bar 以取得其他 MIME 類型。此語法已棄用,建議您使用上述組態。

§Java API

有一個 Http.Context.current().fileMimeTypes() 方法在幕後提供給 Results.sendFile 和其他從檔案副檔名查詢內容類型的函式。無需執行遷移。

§Scala API

play.api.libs.MimeTypes 類別已變更為 play.api.http.FileMimeTypes 介面,而實作已變更為 play.api.http.DefaultFileMimeTypes

所有傳送檔案或資源的結果現在都隱含地採用 FileMimeTypes,即

implicit val fileMimeTypes: FileMimeTypes = ...
Ok(file) // <-- takes implicit FileMimeTypes

FileMimeTypes 的隱含實例由 BaseController(及其子類別 AbstractController 和子特質 InjectedController)透過 ControllerComponents 類別提供,以提供方便的繫結

class SendFileController @Inject() (cc: ControllerComponents) extends AbstractController(cc) {

  def index() = Action { implicit request =>
     val file = readFile()
     Ok(file)  // <-- takes implicit FileMimeTypes
  }
}

您也可以在單元測試中直接取得完全設定的 FileMimeTypes 實例

val httpConfiguration = new HttpConfigurationProvider(Configuration.load(Environment.simple)).get
val fileMimeTypes = new DefaultFileMimeTypesProvider(httpConfiguration.fileMimeTypes).get

或取得自訂實例

val fileMimeTypes = new DefaultFileMimeTypesProvider(FileMimeTypesConfiguration(Map("foo" -> "text/bar"))).get

§預設篩選器

Play 現在附帶一組預設啟用的篩選器,透過設定定義。如果屬性 play.http.filters 為 null,則預設值現在為 play.api.http.EnabledFilters,它會載入由 play.filters.enabled 設定屬性中的完全限定類別名稱定義的篩選器。

在 Play 本身中,play.filters.enabled 為空清單。不過,篩選器函式庫會在 sbt 中自動載入為稱為 PlayFilters 的 AutoPlugin,並會將下列值附加到 play.filters.enabled 屬性

這表示在新的專案中,CSRF 保護(ScalaCsrf / JavaCsrf)、SecurityHeadersAllowedHostsFilter 都會自動定義。

§預設篩選器的效果

預設篩選器設定為提供「預設安全」的專案設定。

您應該保持這些篩選器已啟用:它們讓您的應用程式更安全。

如果您在現有專案中未啟用這些篩選器,則需要一些設定,而且您可能不熟悉相關錯誤和失敗。為了協助移轉,我們將逐一說明每個篩選器、它的功能以及需要的設定。

§CSRFFilter

CSRF 篩選器在 ScalaCsrfJavaCsrf 中說明。它透過將 CSRF 令牌新增到 POST 要求中檢查的表單,來防範 跨網站請求偽造 攻擊。

§它為何預設啟用

CSRF 是一種非常常見的攻擊,實作起來幾乎不需要什麼技巧。您可以在 https://github.com/Manc/play-scala-csrf 上看到一個使用 Play 的 CSRF 攻擊範例。

§我需要進行什麼變更?

如果您從一個現有的專案進行遷移,而該專案未使用 CSRF 表單輔助程式,例如 CSRF.formField,那麼您可能會在 CSRF 篩選器中看到 PUT 和 POST 要求的「403 禁止」。

CSRF.formField 加入您的表單範本將會解決這個錯誤。如果您正在使用 AJAX 進行要求,您可以將 CSRF 令牌放入 HTML 頁面,然後使用 Csrf-Token 標頭將它加入要求中。

若要檢查此行為,請將 <logger name="play.filters.csrf" value="TRACE"/> 加入您的 logback.xml

您可能還想要在 Play 中啟用 SameSite Cookie,這會提供額外的防禦措施來對抗 CSRF 攻擊。

§SecurityHeadersFilter

SecurityHeadersFilter 透過在要求中加入額外的 HTTP 標頭,來防止 跨網站指令碼點選劫持 攻擊。

§它為何預設啟用

基於瀏覽器的攻擊極為常見,而安全性標頭可以提供深入的防禦措施來幫助挫敗這些攻擊。

§我需要進行什麼變更?

預設的「Content-Security-Policy」設定相當嚴格,您可能需要進行一些實驗才能找出最有用的設定。Content-Security-Policy 設定會改變 Javascript 和遠端框架在瀏覽器中的顯示方式。在您修改 Content-Security-Policy 標頭之前,嵌入式 Javascript 或 CSS 將不會載入您的網頁。

如果您確定不想要啟用它,您可以按照下列方式停用 Content-Security-Policy

play.filters.headers.contentSecurityPolicy=null

CSP-Useful 是關於一般 Content-Security-Policy 的良好資源。請注意,還有其他潛在的解決方案可以嵌入 Javascript,例如在每個請求中加入自訂 CSP nonce。

其他標頭的侵入性較低,不太可能在一般網站上造成問題,但可能會在單頁應用程式中造成 cookie 或呈現問題。Mozilla 有文件詳細說明每個標頭,在 URL 中使用標頭名稱:例如,對於 X-Frame-Options,請前往 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options

play.filters.headers {

    # The X-Frame-Options header. If null, the header is not set.
    frameOptions = "DENY"

    # The X-XSS-Protection header. If null, the header is not set.
    xssProtection = "1; mode=block"

    # The X-Content-Type-Options header. If null, the header is not set.
    contentTypeOptions = "nosniff"

    # The X-Permitted-Cross-Domain-Policies header. If null, the header is not set.
    permittedCrossDomainPolicies = "master-only"

    # The Content-Security-Policy header. If null, the header is not set.
    contentSecurityPolicy = "default-src 'self'"

    # The Referrer-Policy header. If null, the header is not set.
    referrerPolicy = "origin-when-cross-origin, strict-origin-when-cross-origin"

    # If true, allow an action to use .withHeaders to replace one or more of the above headers
    allowActionSpecificHeaders = false
}

§AllowedHostsFilter

AllowedHostsFilter 會加入已允許主機的白名單,並對所有主機不符合白名單的請求傳送 400 (錯誤的請求) 回應。

§為什麼預設啟用

這是開發中要使用的重要篩選器,因為 DNS 重新繫結攻擊可以用於開發人員的 Play 實例:請參閱 Rails Webconsole DNS Rebinding,了解短暫的 DNS 重新繫結如何攻擊在 localhost 上執行的伺服器。

§我需要做哪些變更?

如果您在 localhost 以外的地方執行 Play 應用程式,您必須設定 AllowedHostsFilter 以特別允許您連線的主機名稱/IP。在變更環境時,特別要注意這一點,因為通常您會在開發中執行 localhost,但在暫存和製作中會遠端執行。

play.filters.hosts {
  # Allow requests to example.com, its subdomains, and localhost:9000.
  allowed = [".example.com", "localhost:9000"]
}

§加入篩選器

若要附加至預設清單,請使用 +=

play.filters.enabled+=MyFilter

如果您已透過擴充 play.api.http.DefaultHttpFilters 來定義自己的篩選器,您也可以在程式碼中將 EnabledFilters 與您自己的清單結合,因此如果您先前已定義專案,它們仍會照常運作

class Filters @Inject()(enabledFilters: EnabledFilters, corsFilter: CORSFilter)
  extends DefaultHttpFilters(enabledFilters.filters :+ corsFilter: _*)

§測試預設篩選器

由於已啟用多個篩選器,功能測試可能需要稍作變更,以確保所有測試通過且要求有效。例如,未將 Host HTTP 標頭設定為 localhost 的要求不會通過 AllowedHostsFilter,且會傳回 400 禁止回應。

§使用 AllowedHostsFilter 進行測試

由於 AllowedHostsFilter 篩選器會自動加入,功能測試需要加入 Host HTTP 標頭。

如果您使用 FakeRequestHelpers.fakeRequest,則會自動為您加入 Host HTTP 標頭。如果您使用 play.mvc.Http.RequestBuilder,則可能需要加入自己的程式行手動加入標頭

RequestBuilder request = new RequestBuilder()
        .method(GET)
        .header(HeaderNames.HOST, "localhost")
        .uri("/xx/Kiwi");

§使用 CSRFFilter 進行測試

由於 CSRFFilter 篩選器會自動加入,因此會呈現包含 CSRF.formField 的 Twirl 範本的測試,例如

@(userForm: Form[UserData])(implicit request: RequestHeader, m: Messages)

<h1>user form</h1>

@request.flash.get("success").getOrElse("")

@helper.form(action = routes.UserController.userPost()) {
  @helper.CSRF.formField
  @helper.inputText(userForm("name"))
  @helper.inputText(userForm("age"))
  <input type="submit" value="submit"/>
}

要求中必須包含 CSRF 令牌。在 Scala API 中,這是透過匯入 play.api.test.CSRFTokenHelper._ 來完成,這會使用 withCSRFToken 方法豐富 play.api.test.FakeRequest

import play.api.test.CSRFTokenHelper._

class UserControllerSpec extends PlaySpec with GuiceOneAppPerTest {
  "UserController GET" should {

    "render the index page from the application" in {
      val controller = app.injector.instanceOf[UserController]
      val request = FakeRequest().withCSRFToken
      val result = controller.userGet().apply(request)

      status(result) mustBe OK
      contentType(result) mustBe Some("text/html")
    }
  }
}

在 Java API 中,這是透過對 play.mvc.Http.RequestBuilder 執行個體呼叫 CSRFTokenHelper.addCSRFToken 來完成

requestBuilder = CSRFTokenHelper.addCSRFToken(requestBuilder);

§停用預設篩選器

停用預設篩選器的最簡單方法是在 application.conf 中手動設定篩選器清單

play.filters.enabled=[]

這在您有不想通過預設篩選器的功能測試時可能很有用。

如果您想要移除所有篩選器類別,您可以透過 disablePlugins 機制停用它

lazy val root = project.in(file(".")).enablePlugins(PlayScala).disablePlugins(PlayFilters)

或替換 EnabledFilters

play.http.filters=play.api.http.NoHttpFilters

如果您正在撰寫涉及 GuiceApplicationBuilder 的功能測試,並且您想要停用預設篩選器,那麼您可以透過使用 configure 透過設定來停用全部或部分篩選器

GuiceApplicationBuilder().configure("play.http.filters" -> "play.api.http.NoHttpFilters")

§編譯時間預設篩選器

如果您正在使用編譯時間依賴注入,那麼預設篩選器會在編譯時間解析,而不是在執行時間解析。

這表示 BuiltInComponents 特質現在包含一個 httpFilters 方法,它被保留為抽象

trait BuiltInComponents {

  /** A user defined list of filters that is appended to the default filters */
  def httpFilters: Seq[EssentialFilter]
}

預設篩選器清單定義在 play.filters.HttpFiltersComponents

trait HttpFiltersComponents
     extends CSRFComponents
     with SecurityHeadersComponents
     with AllowedHostsComponents {

   def httpFilters: Seq[EssentialFilter] = Seq(csrfFilter, securityHeadersFilter, allowedHostsFilter)
}

在大部分情況下,您會想要混合 HttpFiltersComponents 並附加您自己的篩選器

class MyComponents(context: ApplicationLoader.Context)
  extends BuiltInComponentsFromContext(context)
  with play.filters.HttpFiltersComponents {

  lazy val loggingFilter = new LoggingFilter()
  override def httpFilters = {
    super.httpFilters :+ loggingFilter
  }
}

如果您想要從清單中篩選出元素,您可以執行下列操作

class MyComponents(context: ApplicationLoader.Context)
  extends BuiltInComponentsFromContext(context)
  with play.filters.HttpFiltersComponents {
  override def httpFilters = {
    super.httpFilters.filterNot(_.getClass == classOf[CSRFFilter])
  }
}

§停用編譯時間預設篩選器

若要停用預設篩選器,請混合 play.api.NoHttpFiltersComponents

class MyComponents(context: ApplicationLoader.Context)
   extends BuiltInComponentsFromContext(context)
   with NoHttpFiltersComponents
   with AssetsComponents {

  lazy val homeController = new HomeController(controllerComponents)
  lazy val router = new Routes(httpErrorHandler, homeController, assets)
}

§JWT 支援

Play 的 cookie 編碼已切換為在幕後使用 JSON Web Token (JWT)。JWT 附帶許多優點,特別是使用 HMAC-SHA-256 自動簽署,以及支援自動「不得早於」和「在之後過期」日期檢查,這可確保會話 cookie 無法在給定的時間範圍外重複使用。

更多資訊請參閱 設定 Session Cookie 頁面。

Play 的 Cookie 編碼使用「備用」Cookie 編碼機制,它會讀取 JWT 編碼的 Cookie,如果 JWT 分析失敗,則嘗試讀取 URL 編碼的 Cookie,因此您可以安全地將現有的 Session Cookie 遷移到 JWT。此功能在 FallbackCookieDataCodec 特徵中,並由 DefaultSessionCookieBakerDefaultFlashCookieBaker 採用。

§舊版支援

使用 JWT 編碼的 Cookie 應該是無縫的,但如果您願意,您可以透過在 application.conf 檔案中切換到 play.api.mvc.LegacyCookiesModule 來還原為 URL 編碼的 Cookie 編碼

play.modules.disabled+="play.api.mvc.CookiesModule"
play.modules.enabled+="play.api.mvc.LegacyCookiesModule"

§自訂 CookieBakers

如果您在 Play 中使用自訂 Cookie,使用 CookieBaker[T] 特徵,則需要指定您想要為自訂 Cookie Baker 使用哪種類型的編碼。

Map[String, String] 編碼和解碼到瀏覽器中找到的格式的方法已萃取到 CookieDataCodec。有三個實作:FallbackCookieDataCodecJWTCookieDataCodecUrlEncodedCookieDataCodec,它們分別代表使用 HMAC、JWT 或「讀取簽章或 JWT,寫入 JWT」編解碼器的 URL 編碼。

您還需要提供 JWTConfiguration 案例類別,使用 JWTConfigurationParser 搭配組態路徑,或使用 JWTConfiguration() 取得預設值。

@Singleton
class UserInfoCookieBaker @Inject()(service: UserInfoService,
                                    val secretConfiguration: SecretConfiguration)
  extends CookieBaker[UserInfo] with JWTCookieDataCodec {

  override val COOKIE_NAME: String = "userInfo"

  override val isSigned = true

  override def emptyCookie: UserInfo = new UserInfo()

  override protected def serialize(userInfo: UserInfo): Map[String, String] = service.encrypt(userInfo)

  override protected def deserialize(data: Map[String, String]): UserInfo = service.decrypt(data)

  override val path: String = "/"

  override val jwtConfiguration: JWTConfiguration = JWTConfigurationParser()
}

§已棄用的 Futures 方法

下列 play.libs.concurrent.Futures 靜態方法已棄用

應改用注入相依項的 Futures 實例

class MyClass {
    @Inject
    public MyClass(play.libs.concurrent.Futures futures) {
        this.futures = futures;
    }

    CompletionStage<Double> callWithOneSecondTimeout() {
        return futures.timeout(computePIAsynchronously(), Duration.ofSeconds(1));
    }
}

§已更新的函式庫

§Netty 4.1

Netty 已升級至 版本 4.1。這主要是因為版本 4.0 已被 play-ws 遷移至獨立模組 所遮蔽。因此,如果您正在使用 Netty Server 和一些依賴於 Netty 4.0 的函式庫,我們建議您嘗試升級至較新版本的函式庫,或者您可以開始使用 Akka Server

如果您出於某些原因直接使用 Netty 類別,則應 將您的程式碼調整至這個新版本

§FluentLenium 和 Selenium

FluentLenium 函式庫已更新至版本 3.2.0,而 Selenium 已更新至版本 3.3.1(您可能想在此處查看 變更記錄)。如果您之前使用 Selenium 的 WebDriver API,則不應有任何問題。請查看 公告以取得進一步資訊。
如果您使用 FluentLenium 函式庫,您可能必須變更一些語法才能讓您的測試再次運作。請參閱 FluentLenium 的 遷移指南 以取得有關如何調整程式碼的更多詳細資訊。

§HikariCP

已更新 HikariCP 並導入新的組態:initializationFailTimeout。應使用此新組態取代現已不建議使用的 initializationFailFast。請參閱 HikariCP 變更日誌initializationFailTimeout 文件,以更深入了解如何使用此新組態。

§其他組態變更

有一些組態變更。舊的組態路徑通常仍可使用,但如果您使用它們,執行時期會產生不建議使用的警告。以下是已變更金鑰的摘要

舊金鑰 新金鑰
play.crypto.secret play.http.secret.key
play.crypto.provider play.http.secret.provider
play.websocket.buffer.limit play.server.websocket.frame.maxLength

下一步:訊息遷移


在此文件找到錯誤?此頁面的原始碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時提交拉取請求。有問題或建議要分享?前往 我們的社群論壇 與社群展開對話。