文件

§Play 2.7 遷移指南

這是從 Play 2.6 遷移到 Play 2.7 的指南。如果你需要從 Play 的早期版本遷移,則必須先遵循 Play 2.6 遷移指南

§如何遷移

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

§Play 升級

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

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

其中 2.7.x 中的「x」是你想要使用的 Play 次要版本,例如 2.7.0

§sbt 升級到 1.2.8

儘管 Play 2.7 仍支援 sbt 0.13 系列,但我們建議你從現在開始使用 sbt 1.x。這個新版本會持續維護和支援。若要更新,請變更你的 project/build.properties,使其讀取

sbt.version=1.2.8

撰寫本文時,1.2.8 是 sbt 1.x 系列中的最新版本,您可能也能使用較新的版本。請查看 Play 2.7.x 次要版本的發行說明瞭解詳細資訊。更多資訊請參閱 sbt 發行版本 清單。

§API 變更

我們遵循在移除現有 API 之前將其標示為過時的政策,因此進行了多項 API 變更。本節說明這些變更。

§已移除過時的 API

Play 2.7 中已移除許多在先前版本中標示為過時的 API。如果您仍在使用這些 API,建議您在升級到 Play 2.7 之前移轉到新的 API。Javadoc 和 Scaladoc 通常都有關於如何移轉的適當文件。請參閱 Play 2.6 移轉指南 以取得更多資訊。

§已移除 StaticRoutesGenerator

已移除在 2.6.0 中標示為過時的 StaticRoutesGenerator。如果您仍在使用它,您可能必須從 build.sbt 檔案中移除類似下列這行的內容

routesGenerator := StaticRoutesGenerator

§Java Http.Context 變更

請參閱 play.mvc.Http.Context API 中所做的變更。這只與 Java 使用者相關:Java `Http.Context` 變更

§Play WS 變更

在 Play 2.6 中,我們將大部分 Play-WS 萃取到一個 獨立專案 中,該專案有獨立的發行週期。Play-WS 現在有一個重大發行版本,需要在 Play 本身進行一些變更。

Play-WS 2.0 帶來 Async-Http-Client 的更新版本,它有一個內部 cookie 儲存區,它是全域性的,如果您在要求中向第三方服務傳送使用者敏感的 cookie,它會影響您的應用程式。例如,由於 cookie 儲存區是全域性的,因此應用程式在向同一個主機提出要求時,可能會將使用者的 cookie 與另一個使用者的 cookie 混在一起。現在有一個新的設定,您可以使用它來啟用或停用快取

# Enables global cache cookie store
play.ws.ahc.useCookieStore = true

預設情況下,快取是停用的。這會影響其他地方,例如自動追蹤重新導向。以前,第一個要求的 cookie 會在後續要求中傳送,但在快取停用時並非如此。目前沒有辦法針對每個要求設定快取。

§Scala API

  1. play.api.libs.ws.WSRequest.requestTimeout 現在傳回 Option[Duration],而不是 Option[Int]

§Java API

  1. play.libs.ws.WSRequest.getUsername 現在傳回 Optional<String>,而不是 String
  2. play.libs.ws.WSRequest.getContentType 現在傳回 Optional<String>,而不是 String
  3. play.libs.ws.WSRequest.getPassword 現在傳回 Optional<String>,而不是 String
  4. play.libs.ws.WSRequest.getScheme 現在傳回 Optional<WSScheme,而不是 WSScheme
  5. play.libs.ws.WSRequest.getCalculator 現在傳回 Optional<WSSignatureCalculator>,而不是 WSSignatureCalculator
  6. play.libs.ws.WSRequest.getRequestTimeout 現在傳回 Optional<Duration>,而不是 long
  7. play.libs.ws.WSRequest.getRequestTimeoutDuration 已移除,建議使用 play.libs.ws.WSRequest.getRequestTimeout
  8. play.libs.ws.WSRequest.getFollowRedirects 現在傳回 Optional<Boolean>,而不是 boolean

也新增了一些新方法來改善 Java API

新方法 play.libs.ws.WSResponse.getBodyAsSource 將回應主體轉換為 Source<ByteString, ?>。例如

wsClient.url("https://play.dev.org.tw")
    .stream() // this returns a CompletionStage<StandaloneWSResponse>
    .thenApply(StandaloneWSResponse::getBodyAsSource);

其他方法已新增以改善 Java API

  1. play.libs.ws.WSRequest.getBody 會傳回為該要求所設定的主體。在實作 play.libs.ws.WSRequestFilter 時,這項功能會很有用
  2. play.libs.ws.WSRequest.getMethod 會傳回為該要求所設定的方法。
  3. play.libs.ws.WSRequest.getAuth 會傳回 WSAuth
  4. play.libs.ws.WSRequest.setAuth 會設定該要求的 WSAuth
  5. play.libs.ws.WSResponse.getUri 會取得該回應的 URI

§BodyParsers API 一致性

主體剖析器的 API 會混合使用 IntegerLong 來定義緩衝區長度,這可能會導致值溢位。現在,設定已統一使用 Long。這表示如果您依賴於 play.api.mvc.PlayBodyParsers.DefaultMaxTextLength,例如,您就需要使用 Long。因此,play.api.http.ParserConfiguration.maxMemoryBuffer 現在也是 Long

§剖析器 maxMemoryBuffer 限制

剖析時,某些酬載會在記憶體中擴充。因此,記憶體表示會比從線路上讀取的純文字表示佔用更多空間。JSON 就是其中一種格式。為了防止可能導致記憶體不足錯誤並造成拒絕服務的攻擊,主體剖析和表單繫結必須遵守 play.http.parser.maxMemoryBuffer 設定。

在特定情況下,也可以放寬 maxMemoryBuffer。JSON 表示和擴充表示的大小可能不同,而且您需要使用不同的限制。您可以使用具有自訂限制的表單繫結,方法如下

class MyController @Inject()(cc: ControllerComponents) {

  // This will be the action that handles our form post
  def myMethod = Action { implicit request: Request[_] =>
    // create a new formBinding instance with increased limit 
    val defaultFormBinding: FormBinding = cc.parsers.formBinding(300*1024) // limit to 300KiB
    form.bindFromRequest()(request, formBinding)
    ...
  }

}

控制器將永遠擁有建立的 FormBinding 實例,以遵守 play.http.parser.maxMemoryBuffer。如果您從控制器外部的某些程式碼使用您的表單,您可能需要提供一個隱含的 FormBinding。例如,如果您撰寫單元測試,您可以使用 play.api.data.FormBinding.Implicits._ 中提供的 FormBinding,它使用一個硬編碼的限制,這對於測試來說已經足夠了。在範圍內新增隱含

`scala import play.api.data.FormBinding.Implicits._

§新增欄位和方法到 FilePartFileInfo

ScalaJavaFilePart 類別有兩個新的欄位/方法,提供您透過 multipart/form-data 編碼上傳的檔案大小和配置類型

Scala 的 FileInfo 類別現在也有 dispositionType 欄位。

如果您有包含 FilePartFileInfo 的 Scala case 陳述式,您需要更新這些陳述式,以包含這些新欄位,否則您會收到編譯器錯誤

FilePart
case FilePart(key, filename, contentType, file, fileSize, dispositionType) => ...
// Or if you don't use these new fields:
case FilePart(key, filename, contentType, file, _, _) => ...
FileInfo
case FileInfo(partName, filename, contentType, dispositionType) => ...
// Or if you don't use these new fields:
case FileInfo(partName, filename, contentType, _) => ...

§在使用自訂主體剖析器時,傳遞上傳檔案的大小到 FilePart

Play ScalaPlay Java 中透過 multipart/form-data 編碼上傳檔案時,FilePart 現在會透過 Scala API 中的 fileSize 和 Java API 中的 getFileSize() 揭露上傳檔案的大小。
如果你對檔案上傳使用自訂主體剖析器,則需要自行將檔案大小傳遞給產生的 FilePart 實例。否則,檔案大小將不會設定,並預設為 -1。請參閱自訂 multipart 檔案部分主體剖析器的更新範例 - 在這些範例中,已將處理的位元組數目 (上傳檔案) 傳遞給所建立的 FilePart

§Java 的 FilePart 會揭露上傳檔案的 TemporaryFile

預設情況下,透過 multipart/form-data 編碼上傳檔案會使用 TemporaryFile API,該 API 依賴於將檔案儲存在暫時檔案系統中。
不過,在 Play 2.6 之前,你無法直接存取該 TemporaryFile,只能存取它所備份的 File

Http.MultipartFormData<File> body = request.body().asMultipartFormData();
Http.MultipartFormData.FilePart<File> picture = body.getFile("picture");
if (picture != null) {
    File file = picture.getFile();
}

上述使用的 getFile() 方法現已過時,你應該改用 getRef(),它會提供你一個 TemporaryFile 實例,其中包含 一些有用的方法
從 Play 2.7 開始,上述程式碼應重構為

Http.MultipartFormData<TemporaryFile> body = request.body().asMultipartFormData();
Http.MultipartFormData.FilePart<TemporaryFile> picture = body.getFile("picture");
if (picture != null) {
    TemporaryFile tempFile = picture.getRef();
    File file = tempFile.path().toFile();
}

§TemporaryFile 中新增 copyTo 並重新命名移動方法

在 Play 2.5 之前,moveTo 方法實際上會將檔案複製到目的地並刪除來源。在 Play 2.6 中有一個細微的變更,檔案會在特定條件下以原子方式移動。在這種情況下,來源和目的地都會使用相同的 inode
為了讓 API 在這方面更清楚,現在有一個 copyTo 方法,它總是會建立一個副本,不會共用來源檔案的 inode

Play 2.7 中的另一個變更,是移動檔案的 TemporaryFile 中的方法已重新命名

已棄用的方法 新方法
moveTo(...) moveFileTo(...)
atomicMoveWithFallback(...) atomicMoveFileWithFallback(...)

這些新方法現在會傳回 Path,而不是 TemporaryFile。從這些方法傳回 TemporaryFile 從一開始就是個錯誤,因為有人可能會誤以為這些傳回的檔案是實際的暫時檔案,最終會在某個時間點由 Play 的暫時檔案清除功能自動移除,但事實並非如此。
由於這些方法預計會在將檔案移出 Play 的內部暫存資料夾(上傳檔案最初儲存的位置)時使用,因此最終由開發人員負責處理已移動的目的地檔案(以及是否、如何和何時刪除它)是有道理的。變更傳回類型現在可以釐清這一點。

§Guice 相容性變更

Guice 已升級到 4.2.2 版本(另請參閱 4.2.14.2.0 發行說明),這會造成以下重大變更

§已棄用的靜態 Logger 單例

Java play.Logger 的大部分 static 方法和 Scala play.api.Logger 單例物件的幾乎所有方法都已棄用。這些單例寫入 application 記錄器,在 logback.xml 中參照為

<logger name="application" level="DEBUG" />

如果您擔心變更您的記錄組態,這裡最簡單的遷移方式是使用 Logger("application") (Scala) 或 Logger.of("application") (Java) 定義您自己的單例「應用程式」記錄器。傳送至這個記錄器的所有記錄將會完全像 Play 單例記錄器一樣運作。雖然我們通常不建議這種方法,但最終還是取決於您。Play 和 Logback 沒有強制您對記錄器使用任何特定命名方式。

如果您願意進行一些直接的程式碼變更和變更您的記錄組態,我們建議您為每個類別建立一個新的記錄器,其名稱與類別名稱相符。這讓您可以為每個類別或套件組態不同的記錄層級。例如,若要將所有 com.example.models 的記錄層級設定為資訊層級,您可以在 logback.xml 中設定

<logger name="com.example.models" level="INFO" />

若要在每個類別中定義記錄器,您可以定義

Java
import play.Logger;
private static final Logger.ALogger logger = Logger.of(YourClass.class);
Scala
import play.api.Logger
private val logger = Logger(classOf[YourClass])

對於 Scala,Play 也提供了一個 play.api.Logging 特質,可以混合到類別或特質中,以自動加入 val logger: Logger

import play.api.Logging

class MyClass extends Logging {
  // `logger` is automaticaly defined by the `Logging` trait:
  logger.info("hello!")
}

當然,您也可以直接使用 SLF4J

Java
private static final Logger logger = LoggerFactory.getLogger(YourClass.class);
Scala
private val logger = LoggerFactory.getLogger(classOf[YourClass])

如果您希望在直接使用 SLF4J for Java 時有更簡潔的解決方案,您也可以考慮 Project Lombok 的 @Slf4j 註解

注意:SLF4J 的記錄介面 org.slf4j.Logger 尚未提供接受 lambda 運算式作為參數以進行延遲評估的記錄方法。play.Loggerplay.api.Logger 大多是 org.slf4j.Logger 的簡單包裝器,但提供此類方法。

一旦您從使用 application 記錄器遷移,您就可以移除 logback.xml 中參照它的 logger 項目

<logger name="application" level="DEBUG" />

§Application Loader API 變更

如果您使用自訂 ApplicationLoader,您可能會在執行測試時手動建立這個載入器的實例。為此,您首先需要建立 ApplicationLoader.Context 的實例,例如

val env = Environment.simple()
val context = ApplicationLoader.Context(
  environment = env,
  sourceMapper = None,
  webCommands = new DefaultWebCommands(),
  initialConfiguration = Configuration.load(env),
  lifecycle = new DefaultApplicationLifecycle()
)
val loader = new MyApplicationLoader()
val application = loader.load(context)

但是上述程式碼中使用的 ApplicationLoader.Context 套用方法現在已過時,且當 webCommands 不為空值時會擲回例外。新的程式碼應該是

val env = Environment.simple()
val context = ApplicationLoader.Context.create(env)
val loader = new GreetingApplicationLoader()
val application = loader.load(context)

§JPA 移 除和過時

在 Play 2.6 中已過時的類別 play.db.jpa.JPA,現在終於被移除了。如果您尚未查看,請參閱 Play 2.6 JPA 遷移注意事項

在此 Play 版本中,甚至更多與 JPA 相關的方法和註解已過時

如同在 Play 2.6 JPA 遷移注意事項中已提及,請使用 使用 play.db.jpa.JPAApi 中所述的注入 JPAApi 實例,而不是這些已過時的方法和註解。

§Router#withPrefix 應始終新增前置詞

先前,router.withPrefix(prefix) 用於將前置詞新增到路由器,但仍允許「舊實作」更新其現有的前置詞。Play 的 SimpleRouter 和其他類別遵循此行為。現在已更新所有實作以新增前置詞,因此 router.withPrefix(prefix) 應始終傳回以相同方式路由 s"$prefix/$path" 的路由器,而 router 路由 path

預設情況下,路由器是不帶前綴的,因此,只有當您在已由 withPrefix 傳回的路由器上呼叫 withPrefix 時,這才會導致行為改變。若要取代已設定在路由器上的前綴,您必須在原始未帶前綴的路由器上呼叫 withPrefix,而不是帶前綴的版本。

§執行掛勾

RunHook.afterStarted() 不再將 InetSocketAddress 作為參數。

§所有 Java 表單的 validate 方法都需要移轉到類別層級的約束

Java 表單的「舊」validate 方法將不再執行。
如同 Play 2.6 移轉指南 中所宣布,您必須將此類 validate 方法移轉到 類別層級的約束

重要:升級到 Play 2.7 時,您不會看到任何編譯器警告,指出您必須移轉 validate 方法(因為 Play 會透過反射執行它們)。

§Java FormDynamicFormFormFactory 建構函式已變更

FormDynamicFormFormFactory 類別(位於 play.data 內部)的建構函式(使用 Validator 參數)現在改用 ValidatorFactory 參數。
此外,這些建構函式現在也需要 com.typesafe.config.Config 參數。
例如,new Form(..., validator) 現在變成了 new Form(..., validatorFactory, config)
如果你使用建構函式來實例化表單,而不是只使用 formFactory.form(SomeForm.class),這個變更才會影響你,這很可能發生在測試中。

§Java Cache API 的 get 方法已棄用,建議使用 getOptional

Java cacheApigetOptional 方法會將其結果包裝在 Optional 中傳回。

play.cache.SyncCacheApi 中的變更

已棄用的方法 新方法
<T> T get(String key) <T> Optional<T> getOptional(String key)

play.cache.AsyncCacheApi 中的變更

已棄用的方法 新方法
<T> CompletionStage<T> get(String key) <T> CompletionStage<Optional<T>> getOptional(String key)

§Server.getHandlerFor 已移至 Server#getHandlerFor

路由請求時,Server 特質上的 getHandlerFor 方法由 Play 伺服器程式碼內部使用。它已被移除,並替換為 Server 物件上同名的方法。

§新增 Java DI 不可知的 Play Module API 支援,且所有內建 Java Module 類型已變更

現在你可以透過延伸 play.inject.Module 來建立 Java 的 DI 不可知的 Play Module,這對 Java 來說比較友善,因為它使用 Java API,而且也以 Java 編寫。此外,所有現有的內建 Java Module,例如 play.inject.BuiltInModuleplay.libs.ws.ahc.AhcWSModule,不再延伸 Scala play.api.inject.Module,而是延伸 Java play.inject.Module

由於 Java play.inject.Module 是 Scala play.api.inject.Module 的子類別,因此 Module 實例仍然可以以相同的方式使用,只是介面稍微不同

public class MyModule extends play.inject.Module {
    @Override
    public java.util.List<play.inject.Binding<?>> bindings(final play.Environment environment, final com.typesafe.config.Config config) {
        return java.util.Collections.singletonList(
            // Note: it is bindClass() but not bind()
            bindClass(MyApi.class).toProvider(MyApiProvider.class)
        );
    }
}

§play.mvc.Results.TODO 已移至 play.mvc.Controller.TODO

如果存在 CSPFilter,所有 Play 的錯誤頁面都已更新為呈現 CSP nonce。這表示錯誤頁面範本必須將要求作為參數。在 2.6.x 中,TODO 欄位先前呈現為靜態結果,而不是具有 HTTP 背景的動作,因此可能在控制器外部呼叫。在 2.7.0 中,TODO 欄位已移除,而現在在 play.mvc.Controller 中有一個 TODO(Http.Request request) 方法

public abstract class Controller extends Results implements Status, HeaderNames {
    public static Result TODO(play.mvc.Http.Request request) {
        return status(NOT_IMPLEMENTED, views.html.defaultpages.todo.render(request.asScala()));
    }
}

§內部變更

Play 的內部 API 已進行許多變更。這些 API 在內部使用,且不遵循正常的淘汰程序。可能會在下方提到變更,以協助直接整合 Play 內部 API 的人。

§組態變更

§play.allowGlobalApplication 預設為 false

play.allowGlobalApplication = false 在 Play 2.7.0 中預設設定。這表示在呼叫 Play.current 時會擲回例外。您可以將其設定為 true,以再次讓 Play.current 和其他已淘汰的靜態輔助程式運作,但請注意,此功能將在未來版本中移除。

未來,如果您仍需要使用應用程式元件的靜態執行個體,您可以使用 靜態注入,以使用 Guice 注入它們,或在應用程式載入器中手動設定靜態欄位。只要您小心不要同時執行應用程式(例如,在測試中),這些方法應向前相容於 Play 的未來版本。

由於 Play.current 仍會由一些已淘汰的 API 呼叫,因此在使用此類 API 時,您需要將下列行加入您的 application.conf 檔案

play.allowGlobalApplication = true

例如,在使用嵌入式 Play 和 Scala Sird Routerplay.api.mvc.Action 物件時,它會存取全域狀態

import play.api.mvc._
import play.api.routing.sird._
import play.core.server._

// It can also be NettyServer
val server = AkkaHttpServer.fromRouter() {
  // `Action` in this case is the `Action` object which access global state
  case GET(p"/") => Action {
    Results.Ok(s"Hello World")
  }
}

上述範例需要您設定 play.allowGlobalApplication = true,如前所述,或改寫為

import play.api._
import play.api.mvc._
import play.api.routing.sird._
import play.core.server._

// It can also be NettyServer
val server = AkkaHttpServer.fromRouterWithComponents() { components: BuiltInComponents => {
    case GET(p"/") => components.defaultActionBuilder {
      Results.Ok(s"Hello World")
    }
  }
}

§HikariCP 失敗時不會快速失敗

Play 2.7 將 HikariCP 的 initializationFailTimeout 預設值變更為 -1。這表示即使資料庫不可用,您的應用程式仍會啟動。您可以將 initializationFailTimeout 設定為 1 以回復到舊行為,這將使池快速失敗。

如果應用程式使用資料庫 Evolutions,則會在應用程式啟動時要求建立連線,以驗證是否有新的 Evolutions 要套用。因此,如果資料庫不可用,則會導致啟動失敗,因為需要建立連線。然後,逾時時間將由 connectionTimeout 定義(預設為 30 秒)。

請參閱 SettingsJDBC 以取得更多詳細資料。

§CoordinatedShutdown play.akka.run-cs-from-phase 設定

設定 akka.coordinated-shutdown.exit-jvm 不再受支援。當啟用該設定時,Play 將不會啟動,而且會記錄錯誤。Play 附帶 akka.coordinated-shutdown.* 的預設值,這些值應適用於大多數情況,因此您不太需要覆寫它們。

設定 play.akka.run-cs-from-phase 不再受支援,而且新增它不會影響應用程式關閉。如果它存在,則會記錄警告。Play 現在會執行所有階段,以確保在 ApplicationLifecycle 中註冊的所有掛鉤和所有新增到協調關閉的任務都已執行。如果您需要從特定階段執行 CoordinatedShutdown,您隨時可以手動執行

import akka.actor.ActorSystem
import javax.inject.Inject

import akka.actor.CoordinatedShutdown
import akka.actor.CoordinatedShutdown.Reason

class Shutdown @Inject()(actorSystem: ActorSystem) {

  // Define your own reason to run the shutdown
  case object CustomShutdownReason extends Reason

  def shutdown() = {
    // Use a phase that is appropriated for your application
    val runFromPhase = Some(CoordinatedShutdown.PhaseBeforeClusterShutdown)
    val coordinatedShutdown = CoordinatedShutdown(actorSystem).run(CustomShutdownReason, runFromPhase)
  }
}

對於 Java

import akka.actor.ActorSystem;
import akka.actor.CoordinatedShutdown;

import javax.inject.Inject;
import java.util.Optional;

class Shutdown {

    public static final CoordinatedShutdown.Reason customShutdownReason = new CustomShutdownReason();

    private final ActorSystem actorSystem;

    @Inject
    public Shutdown(ActorSystem actorSystem) {
        this.actorSystem = actorSystem;
    }

    public void shutdown() {
        // Use a phase that is appropriated for your application
        Optional<String> runFromPhase = Optional.of(CoordinatedShutdown.PhaseBeforeClusterShutdown());
        CoordinatedShutdown.get(actorSystem).run(customShutdownReason, runFromPhase);
    }

    public static class CustomShutdownReason implements CoordinatedShutdown.Reason {}
}

§檢查應用程式密碼的最小長度

在執行階段,會檢查應用程式密碼組態 play.http.secret.key 的最小長度。如果金鑰少於 15 個字元,將會記錄警告。如果金鑰少於 8 個字元,則會擲回錯誤,且組態無效。您可以透過將密碼設定為至少 32 位元組的完全隨機輸入,例如 head -c 32 /dev/urandom | base64,或使用應用程式密碼產生器,使用 playGenerateSecretplayUpdateSecret 來解決此錯誤。

應用程式密碼用作確保 Play 會話 Cookie 有效的關鍵,亦即由伺服器產生,而非攻擊者偽造。不過,密碼只指定字串,且不會決定該字串中的熵量。無論如何,只要測量密碼的長度,就可以對密碼中的熵量設定上限:如果密碼長度為 8 個字元,則熵量最多為 64 位元,這對於現代標準來說是不夠的。

§play.filters.headers.contentSecurityPolicy 已棄用,改用 CSPFilter

SecurityHeaders 篩選器有一個 contentSecurityPolicy 屬性:這已在 2.7.0 中棄用。contentSecurityPolicy 已從 default-src 'self' 變更為 nullnull 的預設設定表示不會將 Content-Security-Policy 標頭新增至 SecurityHeaders 篩選器的 HTTP 回應。請使用新的CSPFilter來啟用 CSP 功能。

如果 play.filters.headers.contentSecurityPolicy 不為 null,您將會收到警告。技術上來說,可以同時啟用 contentSecurityPolicy 和新的 CSPFilter,但不建議這麼做。

您可以透過將新的 CSPFilter 加入 play.filters.enabled 屬性來啟用它

play.filters.enabled += play.filters.csp.CSPFilter

注意:您會想要仔細檢閱內容安全政策,以確保它符合您的需求。新的 CSPFilter 明顯比 default-src ‘self’ 允許更多,並且是根據 Google 嚴格的 CSP 設定為基礎。您可以將 report-only 功能與 CSP 報告控制器 搭配使用,以檢閱政策違規事項。

請參閱 CSPFilter 中的文件以取得更多資訊。

在 Play 2.6 中,SameSite Cookie 屬性 已啟用,預設為 session 和 flash。
從 Play 2.7 開始,CSRF 和語言 Cookie 也適用。預設情況下,CSRF Cookie 的 SameSite 屬性將具有與 session Cookie 相同的值,而語言 Cookie 將預設使用 SameSite=Lax
您可以使用設定進行調整。例如

play.filters.csrf.cookie.sameSite = null // no same-site for csrf cookie
play.i18n.langCookieSameSite = "strict" // strict same-site for language cookie

§預設值變更

Play 使用的一些預設值已變更,這可能會對您的應用程式造成影響。本節說明預設變更的詳細資訊。

§application/javascript 作為 JavaScript 的預設內容類型

application/javascript 現在是 JavaScript 的預設內容類型,取代 text/javascript。對於產生的 <script> 標籤,我們現在也省略 type 屬性。在 HTML 5 規格 中查看有關省略 type 屬性的更多詳細資訊。

§自簽署 HTTPS 憑證的變更

它現在在 target/dev-mode/generated.keystore 中產生,而不是直接在根資料夾中。

§text/plain 內容類型中預設字元集的變更

文字和容錯文字主體剖析器現在使用 US-ASCII 作為預設字元集,取代先前的 ISO-8859-1 預設值。

這是因為一些較新的 HTTP 標準,特別是 RFC 7231,附錄 B,其中指出「文字媒體類型的 ISO-8859-1 預設字元集已移除;預設值現在是媒體類型定義所述的任何內容。」text/plain 媒體類型定義是由 RFC 6657,第 4 節 定義的,其中指定 US-ASCII。文字和容錯文字主體剖析器使用 text/plain 作為內容類型,因此現在預設值適當。

§已更新的函式庫

此部分列出對我們的相依項所做的重大更新。

§Akka 更新

Play 2.7 使用 Akka 2.5 系列的最新版本。混合使用 Akka 函式庫的版本 不被允許,並且最新版本會在偵測到使用多個版本的 Akka 產出時記錄警告。您會看到類似下列的內容

Detected possible incompatible versions on the classpath. Please note that a given Akka version MUST be the same across all modules of Akka that you are using, e.g. if you use [2.5.19] all other modules that are released together MUST be of the same version. Make sure you're using a compatible set of libraries. Possibly conflicting versions [2.5.4, 2.5.19] in libraries [akka-actor:2.5.19, akka-remote:2.5.4]

在此範例中,修正方法是將 akka-remote 更新至與 Play 使用的版本相同,例如:

val AkkaVersion = "2.5.19" // should match the version used by Play

libraryDependencies += "com.typesafe.akka" %% "akka-remote" % AkkaVersion

如果您的應用程式使用比 Play 使用的版本更新的版本,您可以在 build.sbt 檔案中 更新 Akka 版本

§HikariCP 更新

HikariCP 已更新至最新版本,最終移除設定檔 initializationFailFast,並以 initializationFailTimeout 取代。請參閱 HikariCP 變更日誌initializationFailTimeout 文件,以更深入了解如何使用此設定檔。

§Guava 版本更新至 27.1-jre

Play 2.6.x 提供 Guava 函式庫的 23.0 版本。現在已更新為最新的實際版本 27.1-jre。函式庫中有許多變更,您可以 在此 查看完整的變更日誌。

§specs2 更新至 4.3.5

先前的版本為 3.8.x。有許多變更和改進,因此我們建議您閱讀 Specs2 最新版本的發行說明。使用的版本已將 Mockito 版本更新至 2.18.x,因此我們也已更新。

§Jackson 更新至 2.9

Jackson 版本已從 2.8 更新至 2.9。此版本的發行說明 在此。此發行版本維持相容性,因此您的應用程式不應受到影響。但您可能會對新功能感興趣。

§Hibernate Validator 更新至 6.0

Hibernate Validator 已更新至 6.0 版,現在相容於 Bean Validation 2.0。請在此處查看新功能 在此處 或閱讀 這篇詳細的部落格文章,了解新版本。

注意:請記住,此版本可能無法與專案中其他 Hibernate 相依性完全相容。例如,如果您使用 hibernate-jpamodelgen,則必須使用最新版本才能確保所有元件都能順利運作

// Visit https://mvnrepository.com/artifact/org.hibernate/hibernate-jpamodelgen to see the list of versions available
libraryDependencies += "org.hibernate" % "hibernate-jpamodelgen" % "5.3.7.Final" % "provided"

§移除的函式庫

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

§移除 BoneCP

BoneCP 已移除。如果您的應用程式組態為使用 BoneCP,則您需要切換至 HikariCP,它是預設的 JDBC 連線池。

play.db.pool = "default"  # Use the default connection pool provided by the platform (HikariCP)
play.db.pool = "hikaricp" # Use HikariCP

您可能需要重新組態池以使用 HikariCP。例如,如果您要組態 HikariCP 的最大連線數,則如下所示。

play.db.prototype.hikaricp.maximumPoolSize = 15

有關更多詳細資訊,請參閱 JDBC 組態區段

此外,您可以使用實作 play.api.db.ConnectionPool 的自訂池,方法是指定完全限定的類別名稱。

play.db.pool=your.own.ConnectionPool

§Apache Commons (commons-lang3commons-codec)

Play 在內部使用了一些 commons-codeccommons-lang3,如果您在專案中使用這些元件,則需要將它們新增至您的 build.sbt

// Visit https://mvnrepository.com/artifact/commons-codec/commons-codec to see the list of versions available
libraryDependencies += "commons-codec" % "commons-codec" % "1.11"

至於 commons-lang3

// Visit https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 to see the list of versions available
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.8.1"

§其他重要變更

§應用程式在 DEV 模式下需要套用演化指令碼時啟動

在 Play 2.6 之前,當資料庫需要在 DEV 模式中執行演化腳本時,應用程式會在啟動時中止。因此,依賴於 ApplicationEvolutions 的模組甚至不會初始化。這表示,如果你在模組中依賴 ApplicationEvolutions,你可以確定在模組初始化時,所有演化腳本都已成功執行,而且你可以從此類模組中插入資料到資料庫,因為演化腳本已建立查詢所需的資料表或其他資料庫物件。

然而,從 Play 2.7 開始,在 DEV 模式中,應用程式(以及所有模組)現在將永遠啟動,無論是否需要套用演化腳本。這表示你無法依賴演化腳本已成功執行,且在模組初始化時,特定資料庫結構可用。
這就是我們新增 ApplicationEvolutions.upToDate 的原因,它表示套用演化的程序是否已完成。只有當該方法傳回 true 時,你才能確定所有演化腳本都已成功執行。upToDate 最終會在某個時間點傳回 true,因為每次你在 DEV 模式中套用或解析演化腳本時,應用程式會自動重新啟動,重新初始化其所有模組。

§演化註解語法

Play Evolutions 現在正確支援 SQL92 註解語法。這表示您可以使用 -- 撰寫 Evolutions,而不是在您選擇的任何地方使用 # 作為行開頭。使用 Evolutions API 新產生的 Evolutions 現在也會在所有區域使用 SQL92 樣式註解語法。文件也已更新,以優先使用 SQL92 樣式,儘管舊的註解樣式仍得到完全支援。

§查詢字串參數繫結行為已變更

§當參數值為空時 (例如 ?myparam=)

定義下列類型查詢字串參數的路由

且封裝在下列類型中

在 Play 2.6 之前,如果請求的查詢字串參數為空 (例如 ?myparam=),此類案例會傳回 400 錯誤請求
這是因為無法從空字串剖析任何上述類型 (例如在 Scala 中,"".toInt 會引發例外狀況,如同其他所有上述類型對其剖析方法所做的一樣)。

從 Play 2.7 開始,將不再有錯誤請求,而是 None (對於 Scala 的 Option)、Optional.empty() (對於 Java 的 Optional) 或空清單會傳遞給此類查詢參數的動作方法。
如果定義了預設值 (例如 myparam: Option[Int] ?= Option(123)),當然會傳遞預設值,而不是傳遞其他值。

注意:如果上述類型未封裝在 OptionOptional 或清單中,預設值行為也會變更,例如 myparam: Int ?= 3,在 Play 2.7 之前也會導致 400 Bad Request,而不是選取預設值。

§當參數完全不存在時

為查詢字串參數定義預設值的路由,這些參數封裝在

當此類請求的查詢字串參數完全不存在時,不會將該預設值傳遞給動作方法。相反,會傳遞 NoneOptional.empty() 或空清單。

從 Play 2.7 開始,預設值現在會傳遞給動作方法,以因應此類不存在的查詢參數。

§multipart/form-data 檔案上傳變更

在 Play 2.6 之前,透過 multipart/form-data 編碼上傳空檔案的處理方式,就像上傳非空檔案一樣。然而,基於顯而易見的原因,上傳空檔案沒有多大意義,因此從 Play 2.7 開始,上傳的空檔案將被視為根本沒有上傳檔案。
因此,透過 Scala APIJava API 擷取上傳檔案時,檔案永遠不會是空的。

注意:如果 multipart/form-data 檔案上傳部分的 filename 標頭為空,則套用相同的邏輯,即使檔案本身不為空。

§Twirl 語法解析改進

為了改善 Twirl 範本的解析,一些到目前為止有效的程式碼將不再受支援。請參閱 問題 以取得更多詳細資訊。

下一步: Java Http.Context 變更


在此文件中發現錯誤?此頁面的原始程式碼可以在 這裡 找到。閱讀 文件指南 後,請隨時貢獻一個 Pull 請求。有問題或建議要分享嗎?前往 我們的社群論壇 與社群展開對話。