diff --git a/manual/Home.md b/manual/Home.md index 80009344..b0eea673 100644 --- a/manual/Home.md +++ b/manual/Home.md @@ -1,35 +1,40 @@ - + # Play %PLAY_VERSION% ドキュメント ->Play は、現代の web アプリケーション開発に必要なコンポーネント及び API を統合した生産性の高い Java と Scala の web アプリケーションフレームワークです。 + +> Play は、現代の web アプリケーション開発に必要なコンポーネント及び API を統合した生産性の高い Java と Scala の web アプリケーションフレームワークです。 > -> Play の特徴は、ライトウェイト、ステートレス、web フレンドリーなアーキテクチャであること、機能予測のしやすさです。また、Iteratee IO をベースにしたリアクティブモデルのおかげで、スケーラブルなアプリケーションでも CPU、メモリ、スレッドなどのリソース消費が最小限になっています。 +> Play の特徴は、ライトウェイト、ステートレス、web フレンドリーなアーキテクチャであること、機能予測のしやすさです。また、Akka Streamsをベースにしたリアクティブモデルのおかげで、スケーラブルなアプリケーションでも CPU、メモリ、スレッドなどのリソース消費が最小限になっています。 + + +## Latest release -- [[Play 2.4 の変更点|Highlights24]] / [[Play 2.4 移行ガイド|Migration24]] -- [[Play 2.3 の変更点|Highlights23]] / [[Play 2.3 移行ガイド|Migration23]] -- [[Play 2.2 の変更点|Highlights22]] / [[Play 2.2 移行ガイド|Migration22]] -- [[Play 2.1 の変更点|Highlights21]] / [[Play 2.1 移行ガイド|Migration21]] +- [[Play2.6の変更点|Highlights26]] +- [[Play2.6移行ガイド|Migration26]] +- [[その他のPlayのリリース|Releases]] @toc@ -## モジュールとプラグイン + +##モジュール -[[一時的なモジュールディレクトリ | ModuleDirectory]] +[[モジュールディレクトリ|ModuleDirectory]] diff --git a/manual/LatestRelease.md b/manual/LatestRelease.md new file mode 100644 index 00000000..96aa1c3c --- /dev/null +++ b/manual/LatestRelease.md @@ -0,0 +1,8 @@ + +# Latest release + +Learn more about the latest Play release. You can download Play releases [here](https://www.playframework.com/download). + +- [[What's new in Play 2.6?|Highlights26]] +- [[Play 2.6 Migration Guide|Migration26]] +- [[Other Play releases|Releases]] diff --git a/manual/ModuleDirectory.md b/manual/ModuleDirectory.md index 9fee9424..b124bc79 100644 --- a/manual/ModuleDirectory.md +++ b/manual/ModuleDirectory.md @@ -1,888 +1,304 @@ - - -# Play モジュール - - -> ここはモジュール一覧が Play ウェブサイト上で登録できるようになるまでの、一時的な公開場所です。 - - -> モジュールを作成するための手順を説明したブログ記事が [objectify.be](http://www.objectify.be/wordpress/?p=363) にあります。 - - -## Airbrake.io notifier - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** airbrake.io に例外通知を送信する - - +# Play modules -* **Website:** -* **Documentation:** -* **Short description:** SES (Simple Email Service) API wrapper for Play ---> -## Amazon SES モジュール (Scala) +Play uses public modules to augment built-in functionality. -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 用 SES (Simple Email Service) API ラッパー +To create your own public module or to migrate from a `play.api.Plugin`, please see [[ScalaPlayModules]] or [[JavaPlayModules]]. - -## Amazon S3 モジュール (Scala) +### swagger-play +* **Website:** +* **Short description:** Generate a Swagger API spec from your Play routes file and Swagger annotations + +### iheartradio/play-swagger +* **Website:** +* **Short description:** Write a Swagger spec in your routes file + +### zalando/play-swagger +* **Website:** +* **Short description:** Generate Play code from a Swagger spec + +### mohiva/swagger-codegen-play-scala +* **Website:** +* **Short description:** Swagger client generator which is based on the PlayWS library + +## Assets + +### play2-sprites +* **Website:** +* **Short description:** play2-sprites is an sbt plugin that generates sprites from images. + +### Sass Plugin +* **Website:** +* **Short description:** Asset handling for [Sass](http://sass-lang.com/) files + +### Typescript Plugin +* **Website:** +* **Short description:** A plugin for sbt that uses sbt-web to compile typescript resources -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 用 S3 (Simple Storage Service) API ラッパー +### play-webpack Plugin +* **Website:** +* **Short description:** A plugin for sbt to handle webpack generated assets and library to render Javascript on the server with Java's nashorn engine. - -## Amf モジュール (Scala) +### Silhouette (Scala) -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 用 AMF (ActionScript Message Format) ラッパー +* **Website:** +* **Documentation:** +* **Short description:** An authentication library that supports several authentication methods, including OAuth1, OAuth2, OpenID, CAS, Credentials, Basic Authentication, Two Factor Authentication or custom authentication schemes. - -## Authentication and Authorization モジュール (Scala) -* **ウェブサイト:** -* **ドキュメント (英):** -* **ドキュメント (日):** -* **概要:** 認証や認可の手段を提供するモジュール -* 2.1-SNAPSHOT サポートもあります: +### Play! Authenticate (Java) - -## Authenticity Token モジュール +### SecureSocial (Java and Scala) -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 1 の authenticity token を復活させ、 CSRF 攻撃を回避する手段を提供する。 +* **Website:** +* **Short description:** An authentication module supporting OAuth, OAuth2, OpenID, Username/Password and custom authentication schemes. - -## ClojureScript プラグイン +### Flyway plugin -* **ウェブサイト:** (ドキュメント、コード) -* **概要:** ClojureScript アサートファイルを JavaScript にコンパイルする +* **Website:** +* **Documentation:** +* **Short Description:** Supports database migration with Flyway. - -## Cloudfront モジュール (Scala) +### MongoDB Morphia Plugin (Java) +* **Website (docs, sample):** +* **Short description:** Provides managed MongoDB access and object mapping using [Morphia](http://morphiaorg.github.io/morphia/) -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play アプリケーションと Cloudfront CDN との統合を補助するモジュール +### MongoDB ReactiveMongo Plugin (Scala) +* **Website (docs, sample):** +* **Short description:** Provides a Play 2.x module for ReactiveMongo, asynchronous and reactive driver for MongoDB. - -## Currency Converter (Java) +### Play-Slick +* **Website (docs, sample):** +* **Short description:** This plugin makes Slick a first-class citizen of Play. -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 用の通貨変換器。現在のレートを取得するためにウェブサービスを利用する。 +### Redis Plugin (Java and Scala) +* **Website (docs, sample):** +* **Short description:** Provides a redis based cache implementation, also lets you use Redis specific APIs - -## Deadbolt 2 プラグイン -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** Deadbolt は、AND/OR/NOT の簡単なシンタックスを使って、特定のコントローラメソッドやビューのパーツにアクセス権を定義する認可メカニズムです。 - - -## DDSL プラグイン - Dynamic Distributed Service Locator -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** DDSL は、ZooKeeper を使用して、インハウスあるいは EC2 やJoyent を利用した独自の動的な "プライベートクラウド" を簡単に構築できるようにします。 - - -## Dust プラグイン -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** クライアントサイドテンプレート言語 Dust のサポート - - -## Google Closure Template プラグイン -* **ウェブサイト (ドキュメント、サンプル):** [https://github.com/gawkermedia/play2-closure](https://github.com/gawkermedia/play2-closure) -* **概要:** Google Closure テンプレートのサポート - - -## Elasticsearch - -* **ウェブサイト:** -* **ドキュメント:** -* **リポジトリ:** -* **概要:** 組み込みの ElasticSearch サーバまたはリモートノード内のオブジェクトをインデクシング/リクエストします - - -## Ember.js - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** ember.js/handlebars テンプレートのプリコンパイルのサポート - - -## funcy - Page Driven Functional Tests (Java) - -* **ウェブサイト:** -* **ドキュメント:** -* **リポジトリ:** -* **概要:** Page Driver クラスを用いた機能テストを簡単に記述できるようにする - - -## FolderMessages プラグイン +* **Website:** +* **Short description:** Provides yet another database access API for Play -* **ウェブサイト:** -* **概要:** ローカライズ用の messages ファイルを分割できるようにして、別々のファイルとしてに管理できるようにする +### Redis Cache Plugin (Java and Scala) - -## Flyway プラグイン -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Flyway を使用したデータベースマイグレーション - -## Geolocation (Java) +### WAR Module -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** IP ベースの地理的位置情報取得モジュール +* **Website:** +* **Documentation:** +* **Short description:** Allow to package Play! 2.x applications into standard WAR packages. - -## Google's HTML Compressor (Java and Scala) -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 2 用の Google 製 HTML 圧縮器 - - -## Groovy Templates プラグイン - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 1 と Play 2 に対応した Groovy テンプレートエンジン - - -## Groovy Templates プラグイン - gt-engine-play2 - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 1 と Play 2 に対応した Groovy テンプレートエンジン。Play 1 の "Faster Groovy Templates" モジュールで使用された gt-engine which を採用している -* **Samples:** - - -## Guice プラグイン (Java and Scala) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** Guice による DI - - -## HTML5 Tags モジュール (Java and Scala) -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** モデルの制約 (required, email pattern, max|min lentgh 等) や特定の input フィールド (date, telephone number, url 等) をもとに、クライアントサイドでバリデーションできるようにする。 - - -## InputValidator (Scala) - -* **ウェブサイト:** -* **概要:** Play 用のシンプルなバリデーション API を提供する - - -## JackRabbit プラグイン (Java and Scala) -* **ウェブサイト (ドキュメント、サンプル):** [JackRabbit Plugin GitHub](https://github.com/sgougi/play21-jackrabbit-plugin) -* **概要:** Play 2 用 Apache JackRabbit プラグイン - - -## Japid モジュール - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 用の Japid java テンプレートを提供する - - -## JsMessages +## Page Rendering -* **ウェブサイト:** -* **概要:** ローカライズされた messages をクライアントサイドで算出できるようにする +### Play Pagelets +* **Website:** +* **Short Description:** A Module for the Play Framework to build resilient and modular Play applications in an elegant and concise manner. +* **Seed project:** - -## JSON minification プラグイン +## Localization -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** JSON を整形・圧縮する +### FolderMessages plugin - -## JSONP filter +### JsMessages -* **ウェブサイト:** -* **概要:** 既存の HTTP API で JSONP を有効化する +* **Website:** +* **Short description:** Allows to compute localized messages on client side. - -## Lessc プラグイン +* **Website:** +* **Documentation:** +* **Short description:** Provides type safety for the project's messages. -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Node を経由して [Less](http://lesscss.org/) をコマンドラインでコンパイルできるようにする +### Play I18n HOCON - -## Liquibase Module -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** アプリケーション起動時に [Liquibase](http://www.liquibase.org/) データベースマイグレーションを実行する +## Performance - -## Manual Dependency Injection プラグイン (Java and Scala) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** 手動で依存性注入できるようにする +### Google's HTML Compressor (Java and Scala) +* **Website:** +* **Documentation:** +* **Short description:** Google's HTML Compressor for Play 2. - -## Memcached プラグイン -* **ウェブサイト:** -* **概要:** memcached をベースにキャッシュを実装できるようにする +## Task Schedulers - -## Messages Compiler プラグイン (Scala) +* **Website**: +* **Documentation**: +* **Short description**: Quartz Extension and utilities for cron-style scheduling in Akka -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** プロジェクトの messages を型安全にする +### play-akkjobs - -## MongoDB Jackson Mapper プラグイン (Java) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** MongoDB への管理されたアクセスと Jackson アノテーションを使用したオブジェクトマッピングを提供する +## Settings - -## MongoDB Jongo プラグイン (Java) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** MongoDB への管理されたアクセスと [Jongo](http://jongo.org/) を使用したオブジェクトマッピング - - -## MongoDB Morphia プラグイン (Java) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** MongoDB への管理されたアクセスと Morphia を使用したオブジェクトマッピング - - -## MongoDB Salat, Casbah プラグイン (Scala) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** MongoDB への管理されたアクセスと Salat と Casbah を使用したオブジェクトマッピング - - -## Mountable routing - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** - - -## Mustache (Java,Scala) - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Mustache テンプレートのサポート - - -## Native Packaging Module - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 2 アプリケーションを標準的システムパッケージ (deb/rpm/msi) としてパッケージできるようにする - - -## NINA (Scala) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** SQL データベースに対し、特に select のようなクエリについて、型安全に発行できるようにする - - -## Origami: OrientDB O/G Mapper (Java and Scala) -* **ウェブサイト (ドキュメント、サンプル):** [Origami Plugin GitHub](https://github.com/sgougi/play21-origami-plugin) -* **概要:** Origami プラグインは、Play 2 Java の OrientDB 用 O/G マッパです。 - - -## PDF モジュール (Java) -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** HTML テンプレートから PDF を生成します +### PlayFOP (Java and Scala) - -## Play! Authenticate (Java) +### Play-Bootstrap (Java and Scala) +* **Website:** +* **Repository:** +* **Short description:** A library for Bootstrap that gives you an out-of-the-box solution with a set of input helpers and field constructors. -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** 高度にカスタマイズ可能な Play 用認証モジュール +### Thymeleaf module (Scala) +* **Website:** +* **Documentation:** +* **Short description:** Allows to use [Thymeleaf](https://www.thymeleaf.org/) template engine as an alternative +to Twirl - -## play2-sprites -* **ウェブサイト:** -* **概要:** play2-sprites は画像からスプライトを生成する sbt プラグインです。 - - -## Play-Bootstrap3 (Java and Scala) -* **ウェブサイト:** -* **ドキュメント:** -* **リポジトリ:** -* **概要:** Play 2.4 で Twitter Bootstrap 3 の HTML コードをレンダリングするための input ヘルパーあるいはフィールドのコンストラクタをまとめました - - -## play-jaxrs (Java) -* **ウェブサイト (ドキュメント、サンプル):** [play-jaxrs](https://github.com/pk11/play-jaxrs/) -* **概要:** Play Java 用 JAX-RS ルータプラグイン - - -## Play-pac4j (Java and Scala) -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** OAuth/CAS/OpenID/HTTP 認証とユーザープロフィールの取得をサポートする Play Scala/Java クライアント - - -## Play Plovr プラグイン -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** Play に Closure コンパイラと Closure ライブラリを追加する - - -## Play-Slick -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** Slick を Play の第一級オブジェクトにする +### Handlebars templates (Java and Scala) + +* **Website:** +* **Documentation:** +* **Short description:** [Handlebars](http://handlebarsjs.com/) templates based on [Java port](https://github.com/jknack/handlebars.java) of handlebars with special handlers for Play Framework. - -## Pusher -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play アプリケーションと Pusher Service サービスとの通信を容易にする - - -## Play Dok - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play アプリケーションと Fukdok PDF テンプレートサービスとの統合を容易にする - - -## Qunit (Java) - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** JavaScript ユニットテストスイート - - -## Redis プラグイン (Java and Scala) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** Redis ベースのキャッシュ実装を提供し、Redis 独自 API も使用できるようにする - - -## Swaggerkit (Scala) -* **ウェブサイト (ドキュメント、サンプル、コード):** -* **概要:** JSON REST API の [Swagger](http://swagger.io/) 仕様書をきれいに出力できるようにする - - -## Emailer プラグイン (Java and Scala) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** apache commons-email をベースとした emailer を提供する - - -## Roy Compiled Asset プラグイン (Ray) -* **ウェブサイト:** -* **ブログ記事:** -* **概要:** [Roy](http://roy.brianmckenna.org/) ファイルを JavaScript にコンパイルする - - -## Sass プラグイン -* **ウェブサイト:** -* **概要:** [Sass](http://sass-lang.com/) ファイルのアサートハンドリング - - -## ScalikeJDBC プラグイン (Scala) -* **ウェブサイト:** -* **概要:** Play の「もう一つのデータベースアクセス API 」を提供する +### Geolocation (Java) - -## SecureSocial (Java and Scala) +### JSONP filter -* **ウェブサイト:** -* **概要:** OAuth, OAuth2, OpenID, Username/Password, そして独自の認証スキームをサポートする認証モジュール +* **Website:** +* **Short description:** Enables JSONP on your existing HTTP API. - -## Silhouette (Scala) +* **Website:** +* **Documentation:** +* **Short description:** Automatic [sitemaps](https://www.sitemaps.org/) generator for Play -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** OAuth1, OAuth2, OpenID, Credentials, Basic認証、二段階認証、カスタム認証スキームなどを提供する認証ライブラリ +### play-guard (Scala) - -## Sitemap Generator (Java) - -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 用 [sitemaps](http://www.sitemaps.org/) 自動生成 - - -## Snapshot プラグイン (Java and Scala) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** Google が [こちら](https://developers.google.com/webmasters/ajax-crawling/docs/html-snapshot) で説明している、HtmlUnit を使った hasg bang スナップショット機能を提供する - - -## socket.io.play (scala only, pre-alpha) - -* **ウェブサイト:** -* **ドキュメント:** - - -## Stateless client authentication (Scala) -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** サーバサイドの状態なしに (状態はクライアントが保持する) 必須あるいはオプションの認証を提供する - - -## Statsd プラグイン (Java and Scala) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** statsd クライアントを提供する - - -## Stylus プラグイン - -* **ウェブサイト:** -* **概要:** [Stylus](https://github.com/learnboost/stylus) による CSS コンパイルのサポート - - -## TinkerPop Frames O/G Mapper プラグイン (Java) -* **ウェブサイト (ドキュメント、サンプル):** GitHub: [Frames-Neo4j Plugin](https://github.com/sgougi/play21-frames-neo4j-plugin) / [Frames-OrientDB Plugin](https://github.com/sgougi/play21-frames-orientdb-plugin) / [Frames-Titan Plugin](https://github.com/sgougi/play21-frames-titan-plugin) -* **概要:** GraphDBs 用 Java O/G マッパ - - -## Typesafe util プラグイン (Scala) -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** リクエストヘッダベースのセキュリティ・シンタックスシュガーを提供するプラグイン - - -## Typesafe SbtGoodies プラグイン -* **ウェブサイト (ドキュメント、サンプル):** -* **概要:** 追加の sbt コマンドを提供するプラグイン - - -## TypeScript プラグイン -* **ウェブサイト:** -* **概要:** [TypeScript](http://www.typescriptlang.org/) ファイルを扱う - - -## WAR Module +## Cloud services -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 2.x アプリケーションを標準 WAR パッケージにまとめる +### Amazon SES module (Scala) - -## XForward モジュール +### Amazon S3 module (Scala) -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 1 のプロキシフォワーディング設定を Play 2 に復活させる +* **Website:** +* **Documentation:** +* **Short description:** S3 (Simple Storage Service) API wrapper for Play - -## XWiki Rendering モジュール (Scala) +* **Website:** +* **Documentation:** +* **Short description:** A reactive module for the Benji library, providing an Object storage DSL (AWS/Ceph S3, Google Cloud Storage). -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Play 用 XWiki レンダリングフレームワーク +### Pusher +* **Website:** +* **Documentation:** +* **Short description:** Easily interact with the Pusher Service within your Play application. - -## Thymeleaf モジュール (Scala) -* **ウェブサイト:** -* **ドキュメント:** -* **概要:** Twirl のかわりに [Thymeleaf](http://www.thymeleaf.org/) テンプレートエンジンを使えるようにする +### Push Notifications module (Java) +* **Website:** +* **Documentation:** +* **Short description:** A stupid-simple module for creating, batching, queuing and sending push notifications. diff --git a/manual/about/Philosophy.md b/manual/about/Philosophy.md index 5afb18b1..2c2c3981 100644 --- a/manual/about/Philosophy.md +++ b/manual/about/Philosophy.md @@ -1,4 +1,4 @@ - + @@ -9,147 +9,64 @@ Since 2007, we have been working on making Java web application development easi --> 2007 年以来、私たちは Java での web アプリケーション の開発を容易なものにしようとしてきました。Play は、 Zenexity (現在は [Zengularity](http://zengularity.com/)) における内部的なプロジェクトとしてスタートし、私たちの web プロジェクトの進め方に強く影響されてきました。つまり、開発者の生産性に焦点を当て、 web のアーキテクチャを尊重し、初めからパッケージング規約に対して斬新なやり方を採用してきたのです - そうすることが理にかなっている場合には、いわゆる JEE のベストプラクティスをも破ってきました。 - -2009 年に、私たちはこれらのアイデアを、オープンソースプロジェクトとしてコミュニティと共有することを決断しました。即座に返されたフィードバックは極めてポジティブなものであり、このプロジェクトは大きな関心を引きつけました。今日 - 2 年間の活発な開発を経て - Play にはいくつかのバージョンができ、10,000 人以上の参加者からなる活発なコミュニティが存在し、世界中で実際に使われているアプリケーションの数は増え続けています。 -世界中に対してプロジェクトを解放するということは、確かにより多くのフィードバックを得られるということではありますが、それはまた、新たなユースケースに出会ってそこから学ぶことや、新たな機能が必要になることや、 元々の設計や前提の下では考慮されていなかったバグが明らかになることでもあります。オープンソースプロジェクトとして Play に取り組んできた 2 年の間に、私たちはこういったすべての問題を修復し、加えて広範囲なシナリオをサポートするための新たな機能を統合してきました。 Play のプロジェクトが成長するにつれて、私たちは Play のコミュニティと、私たち自身の経験から学びました - Play は、どんどん複雑で、多様なプロジェクトで使われるようになってきたのです。 +2009 年に、私たちはこれらのアイデアを、オープンソースプロジェクトとしてコミュニティと共有することを決断しました。即座に返されたフィードバックは極めてポジティブなものであり、このプロジェクトは大きな関心を引きつけました。今日 - 幾年かの活発でパブリックな開発を経て - Play にはいくつかのバージョンができ、10,000 人以上の参加者からなる活発なコミュニティが存在し、世界中で実際に使われているアプリケーションの数は増え続けています。 + +Opening a project to the world certainly means more feedback, but it also means discovering and learning about new use cases, required features and unearthing bugs that were not specifically considered in the original design and its assumptions. During these years of work on Play as an open source project we have worked to fix this kind of issues, as well as to integrate new features to support a wider range of scenarios. As the project has grown, we have learned a lot from our community and from our own experience - using Play in more and more complex and varied projects. - -一方、技術と web の進化の歩みはとどまることを知りません。web は、あらゆるアプリケーションの中心となりました。 HTML, CSS, JavaScript の技術は、急速に発展してきましたーサーバーサイドのフレームワークがついていくことはほとんど不可能なほどです。web のアーキテクチャは、総体として、リアルタイム処理の方向へ急速に向かっており、今日のプロジェクト群に求められるようになった事項からは、データストア技術として SQL を唯一のものとするわけにはいかなくなっていることが分かります。プログラミング言語のレベルにおいては、私たちは、一般的になってきた Scala を含むいくつかの JVM 言語に関連する、後々まで記憶されるような変化の目撃者となりました。 - -それこそが、新時代の web フレームワーク、 Play 2 を開発した理由です。 - -## 非同期プログラミングの構築 - -今日の web アプリケーションは、これまで以上にリアルタイムデータの並行処理を統合するようになってきており、 web フレームワークには完全な非同期 HTTP プログラミングモデルをサポートすることが求められます。 Play はまず、短期間に処理される大量のリクエストを処理する、クラシックな web アプリケーションを扱うように設計されました。しかし今日では、Comet、長期間のポーリング、WebSockets を通じて、接続が保持され続けるコネクションを処理するため、イベントモデルへと進むべきです。 - -Play 2 は、最初からすべてのリクエストが潜在的に長期間保持されるものと見なして設計されています。しかしそれだけではなく、私たちには、長時間にわたって処理されるタスクのスケジューリングと実行を扱う、強力な方法も必要です。 今日、並列度が非常に高いシステムを扱うモデルとしては、Actor ベースのモデルが最良であること、そして Java と Scala の双方で利用可能な Actor ベースのモデルの実装として、Akka が最良のものであることは、疑問の余地がありません - これが、Akka を使う理由です。Play 2 は Play アプリケーションで [Akka](http://akka.io/) をネイティブにサポートし、高度な分散システムを書くことができるようにします。 +Play 2 is architected from the start under the assumption that every request is potentially long-lived. But that’s not all: we also need a powerful way to schedule and run long-running tasks. The Actor-based model is unquestionably the best model today to handle highly concurrent systems, and the best implementation of that model available for both Java and Scala is Akka - so it’s going in. Play 2 provides native [Akka](https://akka.io/) support for Play applications, making it possible to write highly-distributed systems. - -## 型安全性へのフォーカス - -Play のアプリケーションを書くための言語として 静的型付け言語を使う利点の一つは、コンパイラがコードのある部分をチェックできるという点にあります。これは、開発プロセスの早期にミスを検出するのに有効であるのみならず、多くの開発者が参加する大規模なプロジェクトでの作業をとても容易にしてくれます。 - -Play 2 の中に Scala を追加することで、私たちは間違いなく、コンパイラによるさらに強力な保障という利点を得ることになります - しかし、それでもまだ十分ではありません。Play 1.x では、テンプレートシステムは動的なものであり、 Groovy に基づくもので、コンパイラにできることはそれほどありませんでした。その結果、テンプレートで発生するエラーは、実行時にしか検出できなかったのです。これは、コントローラとの間を取り持つコードの検証についても同じことが言えました。 - -私たちは Play 2 において、コードのほとんどをコンパイル時にチェックさせるという考え方をさらに推し進めたいと強く考えています。そのため、私たちは Play のアプリケーションのデフォルトとして、 Scala ベースのテンプレートエンジンを使うことに決めました - これは、Java をメインのプログラミング言語として使う開発者にとっても、です。ただしだからといって、Play 1.x でテンプレートを書くために、Groovy を本当に知っていることが必要だったわけではないのと同様に、Scala のエキスパートにならなければ Play 2 でテンプレートを書くことができないということではありません。 - -Scala が主に使われるのは、Java のシンタックスに極めて近いシンタックスを使って、必要な情報を表示するのにオブジェクトグラフをたどっていくためです。とはいえ、Scala の持つパワーを生かして高度に抽象化されたテンプレートを書きたいなら、式指向で関数型である Scala が、どれほどテンプレートエンジンにぴったりなのかは、すぐに理解できることでしょう。 - -そして、これはテンプレートエンジンにだけ言えることではありません。ルーティングのシステムもまた、完全に型が検査されることになります。Play 2 は、ルートに関するすべての記述をチェックし、リバースルートの部分も含めて、すべてにおいて整合性が保たれているかどうかを検証します。 - -完全にコンパイルが行われることの嬉しい副作用として、テンプレートとルートファイルのパッケージ化と再利用が容易になることと、これらの部分の実行時のパフォーマンスの大幅な向上が見込めるということもあります。 - -## Java 及び Scala のネイティブサポート - -Play 1.1 から、Play のアプリケーションを書くのにプログラミング言語 Scala を使用する可能性を、私たちは探り始めました。この作業は、まずフレームワークそのものに影響を与えることなく、自由に試せるような外部モジュールとして導入されました。 - -Scala を適切に Java ベースのフレームワークに統合することは容易なことではありません。Scala が持つ Java との互換性を考慮すれば、単純に Scala のシンタックスを Java のシンタックスの代わりに使う形で、まず単純に素早く統合してしまうことは可能です。しかしこれは間違いなく、Scala を利用する上で最適な方法ではありません。 Scala は、真のオブジェクト指向と関数型プログラミングを混合したものです。Scala の本当のパワーを解放するには、Play のフレームワークの API の多くを再検討しなければなりません。 - -今では、私たちは個別のモジュールとして Scala をサポートするやり方の限界点に到達しています。私たちが Play 1.x で行った、初期の設計における選択は、Java のリフレクションAPIとバイトコードの操作に強く依存しており、Play の内部の重要な部分のいくつかについて完全に再検討し直さなければ、これ以上の進歩は難しくなっていました。一方で、私たちは Scala モジュールのために、新たな型安全テンプレートエンジンや、まったく新しい SQL アクセスコンポーネントである [Anorm](https://github.com/playframework/anorm) といった、複数の素晴らしいコンポーネントを作成していました。そこで私たちは、 Scala の持つパワーを Play で完全に解放するために、Scala のサポートを個別のモジュールから、Play 2 のコアへ移すことを決断しました。この Play 2 のコアは、初めからプログラミング言語として Scala をネイティブにサポートするよう設計されることになります。 - -一方で、Java に対するサポートが Play 2 から弱くなることはまったくありません。むしろ、完全にその反対なのです。 Play 2 のビルドは、Java の開発者に対し、開発の体験を拡張する機会を提供します。 - -## 強力なビルドシステム - -私たちは初めから、Play のアプリケーションの実行、コンパイル、デプロイについて、斬新な方法を選択してきました。当初、私たちの採った方法は、難解な設計に見えたかも知れません - しかし、標準的な Servlet API の代わりに非同期 HTTP API を提供し、ライブコンパイルと開発中のソースコードのリロードによって短いフィードバックサイクルを提供し、斬新なパッケージングのアプローチを推進することは、極めて重要なことだったのです。その結果として、Play が標準的な JEE の規約に従うことは難しくなりました。 - -今日では、コンテナレスデプロイメントの概念は、Javaの世界において非常に広く受け入れられるようになってきました。この設計上の選択によって、Play framework は Heroku のようなプラットフォームにおいてネイティブに動作できるようになりました。私たちは、[Heroku](https://www.heroku.com/) によって紹介されたモデルは、エラスティックな PaaS プラットフォームにおける Java アプリケーションのデプロイメントの未来だと考えています。 +Today, this idea of container-less deployment is increasingly accepted in the Java world. It’s a design choice that has allowed the Play Framework to run natively on platforms like [Heroku](https://www.heroku.com/), which introduced a model that we consider the future of Java application deployment on elastic PaaS platforms. - -一方で、既存の Java のビルドシステムは、この新しいアプローチをサポートするには、柔軟性が不足していました。私たちは、Play のアプリケーションを実行し、デプロイするための単純明快なツールを提供したいと考えていたことから、Play 1.x では ビルドとデプロイメントのタスクのすべてを処理するために、Python スクリプトの集合体を作り上げました。 - -しかし、ビルドのプロセスのカスタマイズや、企業内の既存のビルドシステムとの統合が求められる、よりエンタープライズ規模のプロジェクトで Play を使っている開発者の方々は、少々困っていました。私たちが Play 1.x で提供していた Python のスクリプト群は、完全な機能を完備したビルドシステムではまったくありませんでしたし、カスタマイズも容易ではありませんでした。これが、私たちが Play 2 でさらに強力なビルドシステムへ舵を切ることを決めた理由です。 - -Play 独自の規約をサポートでき、Java と Scala のプロジェクトをビルドできるだけの柔軟性を持った、現代的なビルドツールが必要だったことから、私たちは [sbt](http://www.scala-sbt.org/) を Play 2 に統合することにしました。ただしこれによって、既存の Play のビルドのシンプルさに満足しているユーザーが脅かされることがあってはなりません。私たちは [Activator](https://www.typesafe.com/get-started) を使って拡張性のあるモデルの上に `activator new`, `run`, `start` などのシンプルなコマンドを提供していますし、アプリケーションのビルドやデプロイの方法を変更する必要がある場合は、Play のプロジェクトは標準的な sbt プロジェクトなので、カスタマイズや特殊な要求への適用に応えるだけのあらゆるパワーを活用できるのです。 +Since we need a modern build tool, flexible enough to support Play original conventions and able to build Java and Scala projects, we have chosen to integrate [sbt](https://www.scala-sbt.org/) in Play 2. sbt is the de facto build tool for Scala and is increasingly accepted in the Java community as well. - -これはまた、Play 2 はインストール直後から、Maven との統合がこれまでよりもうまくできているということでもあり、プロジェクトをシンプルな jar ファイルの集合体としてパッケージ化し、任意のリポジトリへ公開できるということでもあり、さらには依存しているいかなる標準的な Java あるいは Scala ライブラリが開発中の状態であっても、ライブコンパイルやリローディングが可能だということでもあります。 - -## データストアとモデルの統合 - -データストアは、もはや「SQL データベース」の同義語ではありませんし、おそらくはこれまでもそうではありませんでした。データストアの興味深いモデルは、数多くのものが広く使われるようになり、様々なシナリオにおいて、様々な特徴が提供されてきました。そのため、Play のような web フレームワークにとっては、開発者がどのようなデータストアを利用するのか、明確な推測をすることが難しくなってきたのです。Play における汎用モデルの考え方は、単一の API でこういった技術のすべてを抽象化するのはほとんど不可能である以上、もはや意味を成さないものになってしまっています。 - -私たちは Play 2 で、どのようなデータストアドライバ、ORM あるいはその他のデータベースアクセスライブラリも、特にこのフレームワークに統合することなく、容易に利用できるようにしたいと考えています。私たちは単に、コネクションのバインドの管理のような、一般的な技術的課題を扱うための、最小限のヘルパーを提供するようにしたいのです。とはいえ、私たちはまた、特殊な要求を持たないユーザーがクラシックなデータベースへアクセスするためのデフォルトのツールをバンドルすることで、 Play フレームワークのフルスタックという性格も保ち続けたいと思っています。それこそが、 Play 2 が [Ebean](http://ebean-orm.github.io/), JPA, Anorm といったビルドインのリレーショナルデータベースアクセスライブラリを同梱している理由です。 \ No newline at end of file +In Play 2, we wanted to make it really easy to use any data store driver, ORM, or any other database access library without any special integration with the web framework. We simply want to offer a minimal set of helpers to handle common technical issues, like managing the connection bounds. We also want, however, to maintain the full-stack aspect of Play Framework by bundling default tools to access classical databases for users WHO don’t have specialized needs, and that’s why Play 2 comes with built-in relational database access libraries such as [Ebean](http://ebean-orm.github.io/), JPA and Anorm. diff --git a/manual/about/PlayUserGroups.md b/manual/about/PlayUserGroups.md index 672893a7..4d25e7f6 100644 --- a/manual/about/PlayUserGroups.md +++ b/manual/about/PlayUserGroups.md @@ -1,87 +1,44 @@ - - + # Play User Groups ---> -# Play ユーザーグループ - -## ニューヨーク - -http://www.meetup.com/Play-NYC/ - - -## ベルリン -http://www.meetup.com/Play-Berlin-Brandenburg/ +https://www.meetup.com/Play-NYC/ - -## ケルン - -### Scala ユーザーグループ ケルン/ボン - -*(Play について話し合い, プレゼンテーションを行いました。)* [Xing](https://www.xing.com/communities/groups/scala-user-group-koeln-bonn-1035441) / [Twitter](https://twitter.com/scalacgn) - -## ブエノスアイレス +## Vienna - AUSTRIA -http://www.meetup.com/play-argentina/ +https://www.meetup.com/PlayFramework-Wien/ - -## ストックホルム +## Buenos Aires -http://www.meetup.com/play-stockholm/ +https://www.meetup.com/play-argentina/ - -## ベルギー -http://www.play-be.org + - -## 日本 -* http://www.playframework-ja.org/ * https://groups.google.com/forum/?fromgroups#!forum/play_ja - -## 韓国 - -#### 韓国 Play! ユーザグループ * [Facebook](https://www.facebook.com/groups/playuser) * [Github](https://github.com/kpug) * [Slack](https://kpug.slack.com) - - -## ニューデリー - インド -http://www.meetup.com/Reactive-Application-Programmers-in-Delhi-NCR/ +https://www.meetup.com/Reactive-Application-Programmers-in-Delhi-NCR/ diff --git a/manual/about/index.toc b/manual/about/index.toc index 683f9525..abe48d35 100644 --- a/manual/about/index.toc +++ b/manual/about/index.toc @@ -1,2 +1,2 @@ -Philosophy:Play の哲学 -PlayUserGroups:Play ユーザグループ +Philosophy:Playの哲学 +PlayUserGroups:Playユーザグループ \ No newline at end of file diff --git a/manual/detailedTopics/assets/Assets.md b/manual/detailedTopics/assets/Assets.md deleted file mode 100644 index aeb62c3d..00000000 --- a/manual/detailedTopics/assets/Assets.md +++ /dev/null @@ -1,310 +0,0 @@ - - -# 公開アセットを扱う - - -この節は JavaScript, CSS と画像などのアプリケーションの静的リソースの提供をカバーします。 - - -Play で public リソースを提供することは、他の HTTP リクエストにサービスを提供することと同じです。コントローラ/アクションのパスが使う通常のリソースと同じルーティングを使って、クライアントに CSS, JavaScript や画像ファイルを配布します。 - - -## public/ フォルダ - - -規約により、公開アセットはアプリケーションの `public` フォルダに格納されます。このフォルダは好きなように構成できます。以下の構成がおすすめです: - -``` -public - └ javascripts - └ stylesheets - └ images -``` - - -このフォルダ構成に従えば簡単に始められますが、どのように動作するか理解している場合は、自由に変更できます。 - - -## WebJars - - -[WebJars](http://www.webjars.org/) は、Activator や sbt の一部となっている、便利で規約化されたパッケージング機構を提供します。例えば、ビルドファイルに以下のように依存性を追加するだけで、ポピュラーな [Bootstrap ライブラリ](http://getbootstrap.com/) を使うことができます。 - -```scala -libraryDependencies += "org.webjars" % "bootstrap" % "3.3.4" -``` - - -利便性のため、WebJar は依存性を公開アセットフォルダから見た `lib` 関連パスに自動的に展開します。例えば、[RequireJs](http://requirejs.org/) の依存性を宣言している場合、以下のようにしてビューから参照することができます: - -```html - -``` - - -`lib/requirejs/require.js` パスに注目してください。この `lib` フォルダは展開された WebJar アセットを示し、`requirejs` フォルダは WebJar アーティファクト ID に対応していて、そして `require.js` が、この WebJar のルートにある要求されたアセットを参照しています。 - - -## 公開アセットはどのようにパッケージングされる? - - -ビルドプロセス中に `public` フォルダの内容が処理され、アプリケーションのクラスパスに追加されます。 - - -アプリケーションをパッケージングすると、サブプロジェクトを含むすべてのアプリケーションのアセットが単一の `target/my-first-app-1.0.0-assets.jar` に集約されます。この jar はディストリビューションに含まれるので、Play アプリケーションはこれらのアセットを提供することができます。この jar はアセットを CDN やリバースプロキシにデプロイすることもできます。 - - -## アセットコントローラ - - -Play には、公開アセットを提供する組み込みのコントローラが付属しています。このコントローラはデフォルトでキャッシュ機能、ETag, gzip, そして圧縮をサポートします。 - - -このコントローラはデフォルトの Play JAR において `controllers.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") -``` - - -このアクションは、リクエストされたファイルを探し、そのファイルが存在する場合は提供します。 - - -## 公開リソースのリバースルーティング - - -routes ファイルにマッピングされた任意のコントローラと同様に、リバースコントローラが `controllers.routes.Assets` に作成されます。公開リソースを取得するために必要な URL をリバースする際に使用します。テンプレートでの例は以下のようになります: - -```html - -``` - - -以下の結果を生成します。 - -```html - -``` - - -ルートをリバースする際は `folder` パラメータを指定しないことに注意してください。これは、`folder` 引数が固定されている `Assets.at` アクションのマッピングをひとつ、ルートファイルに定義しているためです。そのため、指定する必要はありません。 - - -しかし、 `Assets.at` アクションにマッピングをふたつ定義している場合、次のようにしてください: - -``` -GET /javascripts/*file controllers.Assets.at(path="/public/javascripts", file) -GET /images/*file controllers.Assets.at(path="/public/images", file) -``` - - -リバースルータを使用する場合は、パラメータを両方指定する必要があります: - -```html - - -``` - - -## リバースルーティングと公開アセットへのフィンガープリント - - -[sbt-web](https://github.com/sbt/sbt-web) は、例えば以下のビルドファイルのように、高度に設定可能なアセットパイプラインの概念を Play に持ち込みました: - -```scala -pipelineStages := Seq(rjs, digest, gzip) -``` - - -上記の場合、RequireJs オプティマイザ (`sbt-rjs`), ダイジェスト (`sbt-digest`) と、続けて圧縮 (`sbt-gzip`) が順序付けられます。多くの sbt タスクとは異なり、これらのタスクは宣言された順序で、ひとつずつ実行されます。 - - -アセットのフィンガープリントにより、ブラウザに対して提供する静的なアセットのキャッシュを積極的に指示することができます。結果として、次にサイトを訪れるユーザはより少ないアセットのダウンロードを要求することになり、ユーザ体験が向上します。Rails も [アセットのフィンガープリント](http://railsguides.jp/asset_pipeline.html#%E3%83%95%E3%82%A3%E3%83%B3%E3%82%AC%E3%83%BC%E3%83%97%E3%83%AA%E3%83%B3%E3%83%88%E3%81%A8%E6%B3%A8%E6%84%8F%E7%82%B9) の利点を述べています。 - - -はじめに、上記した `pipelineStages` と、必要なプラグインを `plugins.sbt` に `addSbtPlugin` で宣言します。続いて、バージョン管理するアセットを Play に宣言する必要があります。以下の route ファイルでは、すべてのアセットをバージョン管理するよう宣言しています: - -```scala -GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) -``` - - -> `file: Asset` と書くことで `file` がアセットであると示していることを確認してください。 - - -それから、例えば `scala.html` ビューでリバースルーターを使います: - -```html - -``` - - -アセットフィンガープリントの利用を強く推奨します。 - - -## Etag サポート - - -`Assets` コントローラは、自動的に **ETag** HTTP ヘッダを管理します。ETag の値 (`sbt-digest` がアセットパイプラインで使われている場合) は、ダイジェストまたはリソース名および最終更新日付から生成されます。リソースが JAR ファイルに組み込まれている場合は、その JAR ファイルの最終更新日付が使用されます。 - - -Web ブラウザがこの **ETag** を指定してリクエストを行った場合、サーバは **304 NotModified** で応答することができます。 - - -## Gzip サポート - - -名前が同じで `gz` 拡張子を持つリソースが見つかった場合、`Assets` コントローラは後者のリソース、そして以下の HTTP ヘッダを提供します: - -``` -Content-Encoding: gzip -``` - - -gzip ファイルを生成するには、ビルドに `sbt-gzip` プラグインを取り込み、`pipelineStages` において適切に宣言している必要があります。 - - -## `Cache-Control` 命令の追加 - - -通常のキャッシュ目的であれば、ETag で十分です。特定のリソース用に独自の `Cache-Control` ヘッダを指定したい場合は、`application.conf` ファイルに指定することができます。例えば: - -``` -# Assets configuration -# ~~~~~ -"assets.cache./public/stylesheets/bootstrap.min.css"="max-age=3600" -``` - - -## 管理アセット - - -Play 2.3 から、管理アセットは [sbt-web](https://github.com/sbt/sbt-web#sbt-web) ベースのプラグインで処理されるようになりました。2.3 より前の Play には、管理アセットの処理として CoffeeScript, LESS, JavaScript Lint (ClosureCompiler) そして RequireJS による最適化がバンドルされていました。以降の節では、sbt-web と 2.2 同等の機能を達成する方法について記述します。とは言え、徐々に sbt-web で多くのプラグインが使えるようになっていくため、Play はこのアセット処理技術に制限されていないことに注意してください。[sbt-web](https://github.com/sbt/sbt-web#sbt-web) プロジェクトを確認して、利用できるより多くのプラグインについて学んでください。 - - -多くのプラグインが sbt-web の [js-engine plugin](https://github.com/sbt/sbt-js-engine) を使います。js-engine は、素晴らしい [Trireme](https://github.com/apigee/trireme#trireme) プロジェクトによって JVM 上でも、またはより良いパフォーマンスのために直接 [Node.js](https://nodejs.org/) 上でも Node API で書かれたプラグインを実行することができます。これらのツールは開発サイクルにおいてのみ使用され、Play アプリケーションの実行時には関与しないことに注目してください。Node.js がインストール済みであれば、以下の環境変数を宣言することを推奨します。Unix で `SBT_OPTS` をどこかに定義している場合、以下のように指定することができます: - -```bash -export SBT_OPTS="$SBT_OPTS -Dsbt.jse.engineType=Node" -``` - - -上記の宣言は、あらゆる sbt-web プラグインの実行時に Node.js が使われることを保証します。 diff --git a/manual/detailedTopics/assets/AssetsCoffeeScript.md b/manual/detailedTopics/assets/AssetsCoffeeScript.md deleted file mode 100644 index ba8f6033..00000000 --- a/manual/detailedTopics/assets/AssetsCoffeeScript.md +++ /dev/null @@ -1,72 +0,0 @@ - - -# CoffeeScript を使う - - -[CoffeeScript](http://coffeescript.org/) は、小さくかつエレガントな言語で、JavaScript へコンパイルされます。CoffeeScript は、JavaScript コードを書くためのより良い構文を提供しています。 - - -Play では、コンパイルされるアセットは `app/assets` ディレクトリに定義しなければなりません。このアセットはビルドプロセスによって操作され、CoffeeScript ソースは通常の JavaScript ファイルにコンパイルされます。生成された JavaScript ファイルは標準的なリソースとして、他の管理されないアセットと同じように `public/` フォルダに配布されるので、一度コンパイルされればこれらのリソースの使い方に違いはありません。 - - -例えば、 `app/assets/javascripts/main.coffee` という CoffeeScript ソースファイルは `public/javascripts/main.js` にある通常の JavaScript リソースとして利用できるようになります。 - - -CoffeeScript ソースファイルは `assets` コマンドの実行時や、開発モードでの動作中にブラウザでページを更新すると自動的にコンパイルされます。あらゆるコンパイルエラーはブラウザに表示されます: - -[[images/coffeeError.png]] - - -## ディレクトリ構造 - - -以下は、CoffeeScript を使うプロジェクトのレイアウト例です: - -``` -app - └ assets - └ javascripts - └ main.coffee -``` - - -以下の構文で、コンパイルされた JavaScript ファイルをテンプレートから使うことができます: - -```html - @@ -229,11 +229,11 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-coffeescript" % "1.0.0") Coffeescript options have changed. The new options are: -* `sourceMaps` When set, generates sourceMap files. Defaults to `true`. +* `sourceMap` When set, generates sourceMap files. Defaults to `true`. - `CoffeeScriptKeys.sourceMaps := true` + `CoffeeScriptKeys.sourceMap := true` -* `bare` When set, generates JavaScript without the [top-level function safety wrapper](http://coffeescript.org/#lexical-scope). Defaults to `false`. +* `bare` When set, generates JavaScript without the [top-level function safety wrapper](https://coffeescript.org/#lexical-scope). Defaults to `false`. `CoffeeScriptKeys.bare := false` @@ -354,7 +354,7 @@ mainModule | By default, 'main' is used as the module. modules | The json array of modules. optimize | The name of the optimizer, defaults to uglify2. paths | RequireJS path mappings of module ids to a tuple of the build path and production path. By default all WebJar libraries are made available from a CDN and their mappings can be found here (unless the cdn is set to None). -preserveLicenseComments | Whether to preserve comments or not. Defaults to false given source maps (see http://requirejs.org/docs/errors.html#sourcemapcomments). +preserveLicenseComments | Whether to preserve comments or not. Defaults to false given source maps (see https://requirejs.org/docs/errors.html#sourcemapcomments). webJarCdns | CDNs to be used for locating WebJars. By default "org.webjars" is mapped to "jsdelivr". webJarModuleIds | A sequence of webjar module ids to be used. @@ -492,7 +492,7 @@ The WS API has changed slightly, and `WS.client` now returns an instance of `WSC There are various changes included for Anorm in this new release. -For improved type safety, type of query parameter must be visible, so that it [can be properly converted](https://github.com/playframework/playframework/blob/master/documentation/manual/scalaGuide/main/sql/ScalaAnorm.md#edge-cases). Now using `Any` as parameter value, explicitly or due to erasure, leads to compilation error `No implicit view available from Any => anorm.ParameterValue`. +For improved type safety, type of query parameter must be visible, so that it [[can be properly converted|ScalaAnorm#edge-cases]]. Now using `Any` as parameter value, explicitly or due to erasure, leads to compilation error `No implicit view available from Any => anorm.ParameterValue`. ```scala // Wrong @@ -580,7 +580,7 @@ The session timeout configuration item, `session.maxAge`, used to be an integer, ## Java JUnit superclasses -The Java `WithApplication`, `WithServer` and `WithBrowser` JUnit test superclasses have been modified to define an `@Before` annotated method. This means, previously where you had to explicitly start a fake application by defining: +The Java `WithApplication`, `WithServer` and `WithBrowser` JUnit test superclasses have been modified to define an `@Before` annotated method. This means, previously where you had to explicitly start an application by defining: ```java @Before @@ -589,11 +589,11 @@ public void setUp() { } ``` -Now you don't need to. If you need to provide a custom fake application, you can do so by overriding the `provideFakeApplication` method: +Now you don't need to. If you need to provide a custom application, you can do so by overriding the `provideFakeApplication` method: ```java @Override -protected FakeApplication provideFakeApplication() { +protected Application provideFakeApplication() { return Helpers.fakeApplication(Helpers.inMemoryDatabase()); } ``` diff --git a/manual/releases/release23/index.toc b/manual/releases/release23/index.toc new file mode 100644 index 00000000..867c341b --- /dev/null +++ b/manual/releases/release23/index.toc @@ -0,0 +1,2 @@ +Highlights23:What's new? +Migration23:Migration Guide diff --git a/manual/releases/Highlights24.md b/manual/releases/release24/Highlights24.md similarity index 94% rename from manual/releases/Highlights24.md rename to manual/releases/release24/Highlights24.md index 843a4204..fe4e6b65 100644 --- a/manual/releases/Highlights24.md +++ b/manual/releases/release24/Highlights24.md @@ -1,4 +1,4 @@ - + # What's new in Play 2.4 This page highlights the new features of Play 2.4. If you want learn about the changes you need to make to migrate to Play 2.4, check out the [[Play 2.4 Migration Guide|Migration24]]. @@ -15,7 +15,7 @@ A long term strategy for Play is to remove Play's dependence on global state. P * More interesting deployment scenarios are possible, such as multiple Play instances in a single JVM, or embedding a lightweight Play application. * The application lifecycle becomes easier to follow and reason about. -Removing Play's global state is however a big task that will require some disruptive changes to the way Play applications are written. The approach we are taking to do this is to do as much as possible in Play 2.4 while maintaining backwards compatibility. For a time, many of Play's APIs will support both methods that rely on require global state and methods that don't rely on global state, allowing you to migrate your application to not depend on global state incrementally, rather than all at once when you uprgade to Play 2.4. +Removing Play's global state is however a big task that will require some disruptive changes to the way Play applications are written. The approach we are taking to do this is to do as much as possible in Play 2.4 while maintaining backwards compatibility. For a time, many of Play's APIs will support both methods that rely on require global state and methods that don't rely on global state, allowing you to migrate your application to not depend on global state incrementally, rather than all at once when you upgrade to Play 2.4. The first step to removing global state is to make it such that Play components have their dependencies provided to them, rather than looking them up statically. This means providing out of the box support for dependency injection. @@ -48,9 +48,9 @@ You can read about these new APIs here: It is now straightforward to embed a Play application. Play 2.4 provides both APIs to start and stop a Play server, as well as routing DSLs for Java and Scala so that routes can be embedded directly in code. -In Java, see [[Embedding Play|JavaEmbeddingPlay]] as well as information about the [[Routing DSL|JavaRoutingDsl]]. +In Java, see [[Embedding Play|ScalaEmbeddingPlayAkkaHttp]] as well as information about the [[Routing DSL|JavaRoutingDSL]]. -In Scala, see [[Embedding Play|ScalaEmbeddingPlay]] as well as information about the [[String Interpolating Routing DSL|ScalaSirdRouter]]. +In Scala, see [[Embedding Play|ScalaEmbeddingPlayAkkaHttp]] as well as information about the [[String Interpolating Routing DSL|ScalaSirdRouter]]. ## Aggregated reverse routers @@ -87,7 +87,7 @@ return promise(() -> longComputation()) ## Maven/sbt standard layout -Play will now let you use either its default layout or the directory layout that is the default for Maven and SBT projects. See the [[Anatomy of a Play application|Anatomy]] page for more details. +Play will now let you use either its default layout or the directory layout that is the default for Maven and sbt projects. See the [[Anatomy of a Play application|Anatomy]] page for more details. ## Anorm diff --git a/manual/experimental/ReactiveStreamsIntegration.md b/manual/releases/release24/ReactiveStreamsIntegration.md similarity index 83% rename from manual/experimental/ReactiveStreamsIntegration.md rename to manual/releases/release24/ReactiveStreamsIntegration.md index f7f03e73..a9dbc059 100644 --- a/manual/experimental/ReactiveStreamsIntegration.md +++ b/manual/releases/release24/ReactiveStreamsIntegration.md @@ -1,9 +1,9 @@ - + # Reactive Streams integration (experimental) > **Play experimental libraries are not ready for production use**. APIs may change. Features may not work properly. -[Reactive Streams](http://www.reactive-streams.org/) is a new standard that gives a common API for asynchronous streams. Play 2.4 introduces some wrappers to convert Play's [[Iteratees and Enumerators|Iteratees]] into Reactive Streams objects. This means that Play can integrate with other software that supports Reactive Streams, e.g. [Akka Streams](http://doc.akka.io/docs/akka-stream-and-http-experimental/current/), [RxJava](https://github.com/ReactiveX/RxJavaReactiveStreams) and [others](http://www.reactive-streams.org/announce-1.0.0#implementations). +[Reactive Streams](http://www.reactive-streams.org/) is a new standard that gives a common API for asynchronous streams. Play 2.4 introduces some wrappers to convert Play's [[Iteratees and Enumerators|Iteratees]] into Reactive Streams objects. This means that Play can integrate with other software that supports Reactive Streams, e.g. [Akka Streams](https://doc.akka.io/docs/akka/2.4.3/scala/stream/index.html), [RxJava](https://github.com/ReactiveX/RxJavaReactiveStreams) and [others](http://www.reactive-streams.org/announce-1.0.0#implementations). The purpose of the API is: @@ -31,7 +31,7 @@ Include the Reactive Streams integration library into your project. libraryDependencies += "com.typesafe.play" %% "play-streams-experimental" % "%PLAY_VERSION%" ``` -All access to the module is through the [`Streams`](api/scala/play/api/libs/streams/Streams$.html) object. +All access to the module is through the `Streams` object. Here is an example that adapts a `Future` into a single-element `Publisher`. @@ -40,12 +40,10 @@ val fut: Future[Int] = Future { ... } val pubr: Publisher[Int] = Streams.futureToPublisher(fut) ``` -See the `Streams` object's [API documentation](api/scala/play/api/libs/streams/Streams$.html) for more information. +See the `Streams` object's API documentation for more information. For more examples you can look at the code used by the experimental [[Akka HTTP server backend|AkkaHttpServer]]. Here are the main files where you can find examples: - - * [ModelConversion](https://github.com/playframework/playframework/blob/2.4.x/framework/src/play-akka-http-server/src/main/scala/play/core/server/akkahttp/ModelConversion.scala) * [AkkaStreamsConversion](https://github.com/playframework/playframework/blob/2.4.x/framework/src/play-akka-http-server/src/main/scala/play/core/server/akkahttp/AkkaStreamsConversion.scala) * [AkkaHttpServer](https://github.com/playframework/playframework/blob/2.4.x/framework/src/play-akka-http-server/src/main/scala/play/core/server/akkahttp/AkkaHttpServer.scala) diff --git a/manual/releases/release24/index.toc b/manual/releases/release24/index.toc new file mode 100644 index 00000000..c24236b1 --- /dev/null +++ b/manual/releases/release24/index.toc @@ -0,0 +1,3 @@ +Highlights24:What's new? +!migration24:Migration Guides +ReactiveStreamsIntegration:Reactive Streams integration (experimental) \ No newline at end of file diff --git a/manual/releases/migration24/Anorm.md b/manual/releases/release24/migration24/Anorm.md similarity index 98% rename from manual/releases/migration24/Anorm.md rename to manual/releases/release24/migration24/Anorm.md index 846f6d9a..a876b29c 100644 --- a/manual/releases/migration24/Anorm.md +++ b/manual/releases/release24/migration24/Anorm.md @@ -1,4 +1,4 @@ - + # Anorm Anorm has been pulled out of the core of Play into a separately managed project that can have its own lifecycle. To add a dependency on it, use: @@ -192,4 +192,4 @@ Binary and large data can also be used as parameters: - **Joda Time**: New conversions for Joda `Instant` or `DateTime`, from `Long`, `Date` or `Timestamp` column. - Parses text column as `UUID` value: `SQL("SELECT uuid_as_text").as(scalar[UUID].single)`. -- Passing `None` for a nullable parameter is deprecated, and typesafe `Option.empty[T]` must be use instead. \ No newline at end of file +- Passing `None` for a nullable parameter is deprecated, and typesafe `Option.empty[T]` must be use instead. diff --git a/manual/releases/migration24/GlobalSettings.md b/manual/releases/release24/migration24/GlobalSettings.md similarity index 94% rename from manual/releases/migration24/GlobalSettings.md rename to manual/releases/release24/migration24/GlobalSettings.md index ef96552f..8fae2e8a 100644 --- a/manual/releases/migration24/GlobalSettings.md +++ b/manual/releases/release24/migration24/GlobalSettings.md @@ -1,4 +1,4 @@ - + # Removing `GlobalSettings` If you are keen to use dependency injection, we are recommending that you move out of your `GlobalSettings` implementation class as much code as possible. Ideally, you should be able to refactor your code so that it is possible to eliminate your `GlobalSettings` class altogether. @@ -9,7 +9,7 @@ Next follows a method-by-method guide for refactoring your code. Because the API ## Scala -* `GlobalSettings.beforeStart` and `GlobalSettings.onStart`: Anything that needs to happen on start up should now be happening in the constructor of a dependency injected class. A class will perform its initialisation when the dependency injection framework loads it. If you need eager initialisation (because you need to execute some code *before* the application is actually started), [[define an eager binding|ScalaDependencyInjection#Eager-bindings]]. +* `GlobalSettings.beforeStart` and `GlobalSettings.onStart`: Anything that needs to happen on start up should now be happening in the constructor of a dependency injected class. A class will perform its initialization when the dependency injection framework loads it. If you need eager initialization (because you need to execute some code *before* the application is actually started), [[define an eager binding|ScalaDependencyInjection#Eager-bindings]]. * `GlobalSettings.onStop`: Add a dependency to [`ApplicationLifecycle`](api/scala/play/api/inject/ApplicationLifecycle.html) on the class that needs to register a stop hook. Then, move the implementation of your `GlobalSettings.onStop` method inside the `Future` passed to the `ApplicationLifecycle.addStopHook`. Read [[Stopping/cleaning-up|ScalaDependencyInjection#Stopping/cleaning-up]] for more information. @@ -49,13 +49,13 @@ Also, mind that if your `Global` class is mixing the `WithFilters` trait, you sh ## Java -* `GlobalSettings.beforeStart` and `GlobalSettings.onStart`: Anything that needs to happen on start up should now be happening in the constructor of a dependency injected class. A class will perform its initialisation when the dependency injection framework loads it. If you need eager initialisation (for example, because you need to execute some code *before* the application is actually started), [[define an eager binding|JavaDependencyInjection#Eager-bindings]]. +* `GlobalSettings.beforeStart` and `GlobalSettings.onStart`: Anything that needs to happen on start up should now be happening in the constructor of a dependency injected class. A class will perform its initialization when the dependency injection framework loads it. If you need eager initialization (for example, because you need to execute some code *before* the application is actually started), [[define an eager binding|JavaDependencyInjection#Eager-bindings]]. * `GlobalSettings.onStop`: Add a dependency to [`ApplicationLifecycle`](api/java/play/inject/ApplicationLifecycle.html) on the class that needs to register a stop hook. Then, move the implementation of your `GlobalSettings.onStop` method inside the `Promise` passed to the `ApplicationLifecycle.addStopHook`. Read [[Stopping/cleaning-up|JavaDependencyInjection#Stopping/cleaning-up]] for more information. * `GlobalSettings.onError`: Create a class that inherits from [`HttpErrorHandler`](api/java/play/http/HttpErrorHandler.html), and move the implementation of your `GlobalSettings.onError` inside the `HttpErrorHandler.onServerError` method. Read [[Error Handling|JavaErrorHandling]] for more information. -* `GlobalSettings.onRequest`: Create a class that inherits from [`DefaultHttpRequestHandler`](api/java/play/http/DefaultHttpRequestHandler.html), and move the implementation of your `GlobalSettings.onRequest` method inside the `DefaultHttpRequestHandler.createAction` method. Read [[Request Handlers|JavaHttpRequestHandlers]] for more information. +* `GlobalSettings.onRequest`: Create a class that inherits from [`DefaultHttpRequestHandler`](api/java/play/http/DefaultHttpRequestHandler.html), and move the implementation of your `GlobalSettings.onRequest` method inside the `DefaultHttpRequestHandler.createAction` method. Read [[Request Handlers|JavaActionCreator]] for more information. * `GlobalSettings.onRouteRequest`: There is no simple migration for this method when using the Java API. If you need this, you will have to keep your Global class around for a little longer. @@ -79,4 +79,4 @@ if(statusCode == play.mvc.Http.Status.BAD_REQUEST) { * `GlobalSettings.onLoadConfig`: Specify all configuration in your config file or create your own ApplicationLoader (see [[GuiceApplicationBuilder.loadConfig|JavaDependencyInjection#Advanced:-Extending-the-GuiceApplicationLoader]]). -* `GlobalSettings.filters`: Create a class that inherits from [`HttpFilters`](api/java/play/http/HttpFilters.html), and provide an implementation for `HttpFilter.filters`. Read [[Http Filters|JavaHttpFilters]] for more information. \ No newline at end of file +* `GlobalSettings.filters`: Create a class that inherits from [`HttpFilters`](api/java/play/http/HttpFilters.html), and provide an implementation for `HttpFilter.filters`. Read [[Http Filters|JavaHttpFilters]] for more information. diff --git a/manual/releases/migration24/Migration24.md b/manual/releases/release24/migration24/Migration24.md similarity index 82% rename from manual/releases/migration24/Migration24.md rename to manual/releases/release24/migration24/Migration24.md index e9f4e2fe..cae28fe0 100644 --- a/manual/releases/migration24/Migration24.md +++ b/manual/releases/release24/migration24/Migration24.md @@ -1,8 +1,14 @@ - + # Play 2.4 Migration Guide This is a guide for migrating from Play 2.3 to Play 2.4. If you need to migrate from an earlier version of Play then you must first follow the [[Play 2.3 Migration Guide|Migration23]]. +As well as the information contained on this page, there are is more detailed migration information for some topics: + +- [[Migrating GlobalSettings|GlobalSettings]] +- [[Migrating Plugins to Modules|PluginsToModules]] +- [[Migrating Anorm|Anorm]] + ## Java 8 support The support for Java 6 and Java 7 was dropped and Play 2.4 now requires Java 8. This decision was made based on the fact that [Java 7 reached its End-of-Life in April 2015](https://www.java.com/en/download/faq/java_7.xml). Also, Java 8 enables clean APIs and has better support for functional programming style. If you try to use Play 2.4 with Java 6/7, you will get an error like below: @@ -13,7 +19,7 @@ java.lang.UnsupportedClassVersionError: play/runsupport/classloader/ApplicationC A [java.lang.UnsupportedClassVersionError](https://docs.oracle.com/javase/8/docs/api/java/lang/UnsupportedClassVersionError.html) means that reading a Java class file with an older version of Java than the class file was compiled with is unsupported. -> **Note:** Scala 2.10 does not have full support to all Java 8 language features, like static methods on interfaces. If your project has Java code using these new features present in Java 8, upgrade to use Scala 2.11.6+. See [sbt docs](http://www.scala-sbt.org/0.13/docs/Howto-Scala.html) to learn how to set `scalaVersion` to your project. +> **Note:** Scala 2.10 does not have full support to all Java 8 language features, like static methods on interfaces. If your project has Java code using these new features present in Java 8, upgrade to use Scala 2.11.6+. See [sbt docs](https://www.scala-sbt.org/0.13/docs/Howto-Scala.html) to learn how to set `scalaVersion` to your project. ## Build changes @@ -24,7 +30,7 @@ The following steps need to be taken to update your sbt build before you can loa Update the Play version number in `project/plugins.sbt` to upgrade Play: ```scala -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "%PLAY_VERSION%") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.0") // Set to latest version of Play 2.4 ``` ### sbt upgrade @@ -37,12 +43,9 @@ sbt.version=0.13.8 ### Specs2 support in a separate module -If you were previously using Play's specs2 support, you now need to explicitly add a dependency on that to your project. Additionally, specs2 now requires `scalaz-stream` which isn't available on maven central or any other repositories that sbt uses by default, so you need to add the `scalaz-stream` repository as a resolver: - +If you were previously using Play's specs2 support, you now need to explicitly add a dependency on that to your project: ```scala libraryDependencies += specs2 % Test - -resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" ``` If you are using a .scala build file, you will need to add the following import `import play.sbt.PlayImport._` @@ -68,13 +71,13 @@ Eclipse support can be setup with as little as one extra line to import the plug IntelliJ is now able to import sbt projects natively, so we recommend using that instead. Alternatively, the sbt-idea plugin can be manually installed and used, instructions can be found [here](https://github.com/mpeltonen/sbt-idea). -### Play SBT plugin API +### Play sbt plugin API -All classes in the SBT plugin are now in the package `play.sbt`, this is particularly pertinent if using `.scala` files to configure your build. You will need to import identifiers from `play.sbt.PlayImport` to use play provided configuration elements. +All classes in the sbt plugin are now in the package `play.sbt`, this is particularly pertinent if using `.scala` files to configure your build. You will need to import identifiers from `play.sbt.PlayImport` to use play provided configuration elements. #### `playWatchService` renamed -The SBT setting key `playWatchService` has been renamed to `fileWatchService`. +The sbt setting key `playWatchService` has been renamed to `fileWatchService`. Also the corresponding class has changed. To set the FileWatchService to poll every two seconds, use it like this: ```scala @@ -169,7 +172,7 @@ If you wish to switch to the injected generator, add the following to your build routesGenerator := InjectedRoutesGenerator ``` -By default Play will automatically handle the wiring of this router for you using Guice, but depending in the DI approach you're taking, you may be able to customise it. +By default Play will automatically handle the wiring of this router for you using Guice, but depending in the DI approach you're taking, you may be able to customize it. The injected routes generator also supports the `@` operator on routes, but it has a slightly different meaning (since everything is injected), if you prefix a controller with `@`, instead of that controller being directly injected, a JSR 330 `Provider` for that controller will be injected. This can be used, for example, to eliminate circular dependency issues, or if you want a new action instantiated per request. @@ -185,13 +188,13 @@ While Play 2.4 won't force you to use the dependency injected versions of compon | ------- | --------| -------- | | [`Lang`](api/scala/play/api/i18n/Lang$.html) | [`Langs`](api/scala/play/api/i18n/Langs.html) | | | [`Messages`](api/scala/play/api/i18n/Messages$.html) | [`MessagesApi`](api/scala/play/api/i18n/MessagesApi.html) | Using one of the `preferred` methods, you can get a [`Messages`](api/scala/play/api/i18n/Messages.html) instance. | -| [`DB`](api/scala/play/api/db/DB$.html) | [`DBApi`](api/scala/play/api/db/DBApi.html) or better, [`Database`](api/scala/play/api/db/Database.html) | You can get a particular database using the `@NamedDatabase` annotation. | -| [`Cache`](api/scala/play/api/cache/Cache$.html) | [`CacheApi`](api/scala/play/api/cache/CacheApi.html) or better | You can get a particular cache using the `@NamedCache` annotation. | -| [`Cached` object](api/scala/play/api/cache/Cached$.html) | [`Cached` instance](api/scala/play/api/cache/Cached.html) | Use an injected instance instead of the companion object. You can use the `@NamedCache` annotation. | +| `DB` | [`DBApi`](api/scala/play/api/db/DBApi.html) or better, [`Database`](api/scala/play/api/db/Database.html) | You can get a particular database using the `@NamedDatabase` annotation. | +| `Cache` | [`CacheApi`](api/scala/play/api/cache/CacheApi.html) or better | You can get a particular cache using the `@NamedCache` annotation. | +| `Cached` object | [`Cached` instance](api/scala/play/api/cache/Cached.html) | Use an injected instance instead of the companion object. You can use the `@NamedCache` annotation. | | [`Akka`](api/scala/play/api/libs/concurrent/Akka$.html) | N/A | No longer needed, just declare a dependency on `ActorSystem` | -| [`WS`](api/scala/play/api/libs/ws/WS$.html) | [`WSClient`](api/scala/play/api/libs/ws/WSClient.html) | | -| [`Crypto`](api/scala/play/api/libs/Crypto$.html) | [`Crypto`](api/scala/play/api/libs/Crypto.html) | | -| [`GlobalSettings`](api/scala/play/api/GlobalSettings.html) | [`HttpErrorHandler`](api/scala/play/api/http/HttpErrorHandler.html), [`HttpRequestHandler`](api/scala/play/api/http/HttpRequestHandler.html), and [`HttpFilters`](api/scala/play/api/http/HttpFilters.html)| Read the details in the [[GlobalSettings|Migration24#GlobalSettings]] section below. | +| `WS` | [`WSClient`](api/scala/play/api/libs/ws/WSClient.html) | | +| `Crypto` | `Crypto` | | +| `GlobalSettings` | [`HttpErrorHandler`](api/scala/play/api/http/HttpErrorHandler.html), [`HttpRequestHandler`](api/scala/play/api/http/HttpRequestHandler.html), and [`HttpFilters`](api/scala/play/api/http/HttpFilters.html)| Read the details in the [[GlobalSettings|Migration24#GlobalSettings]] section below. | #### Java @@ -199,21 +202,21 @@ While Play 2.4 won't force you to use the dependency injected versions of compon | ------- | --------| -------- | | [`Lang`](api/java/play/i18n/Lang.html) | [`Langs`](api/java/play/i18n/Langs.html) | Instances of `Lang` objects are still fine to use | | [`Messages`](api/java/play/i18n/Messages.html) | [`MessagesApi`](api/java/play/i18n/MessagesApi.html) | Using one of the `preferred` methods, you can get a `Messages` instance, and you can then use `at` to get messages for that lang. | -| [`DB`](api/java/play/db/DB.html) | [`DBApi`](api/java/play/db/DBApi.html) or better, [`Database`](api/java/play/db/Database.html) | You can get a particular database using the [`@NamedDatabase`](api/java/play/db/NamedDatabase.html) annotation. | +| `DB` | [`DBApi`](api/java/play/db/DBApi.html) or better, [`Database`](api/java/play/db/Database.html) | You can get a particular database using the [`@NamedDatabase`](api/java/play/db/NamedDatabase.html) annotation. | | [`JPA`](api/java/play/db/jpa/JPA.html) | [`JPAApi`](api/java/play/db/jpa/JPAApi.html) | | -| [`Cache`](api/java/play/cache/Cache.html) | [`CacheApi`](api/java/play/cache/CacheApi.html) | You can get a particular cache using the [`@NamedCache`](api/java/play/cache/NamedCache.html) annotation. | +| `Cache` | [`CacheApi`](api/java/play/cache/CacheApi.html) | You can get a particular cache using the [`@NamedCache`](api/java/play/cache/NamedCache.html) annotation. | | [`Akka`](api/java/play/libs/Akka.html) | N/A | No longer needed, just declare a dependency on `ActorSystem` | | [`WS`](api/java/play/libs/ws/WS.html) | [`WSClient`](api/java/play/libs/ws/WSClient.html) | | -| [`Crypto`](api/java/play/libs/Crypto.html) | [`Crypto`](api/java/play/libs/Crypto.html) | The old static methods have been removed, an instance can statically be accessed using `play.Play.application().injector().instanceOf(Crypto.class)` | -| [`GlobalSettings`](api/java/play/GlobalSettings.html) | [`HttpErrorHandler`](api/java/play/http/HttpErrorHandler.html), [`HttpRequestHandler`](api/java/play/http/HttpRequestHandler.html), and [`HttpFilters`](api/java/play/http/HttpFilters.html)| Read the details in the [[GlobalSettings|Migration24#GlobalSettings]] section below. | +| `Crypto` | `Crypto` | The old static methods have been removed, an instance can statically be accessed using `play.Play.application().injector().instanceOf(Crypto.class)` | +| `GlobalSettings` | [`HttpErrorHandler`](api/java/play/http/HttpErrorHandler.html), [`HttpRequestHandler`](api/java/play/http/HttpRequestHandler.html), and [`HttpFilters`](api/java/play/http/HttpFilters.html)| Read the details in the [[GlobalSettings|Migration24#GlobalSettings]] section below. | ### GlobalSettings -If you are keen to use dependency injection, we are recommending that you move out of your `GlobalSettings` implementation class as much code as possible. Read the [[`GlobalSettings` migration documentation|GlobalSettings]] for the glory details. +If you are keen to use dependency injection, we are recommending that you move out of your `GlobalSettings` implementation class as much code as possible. Read the [[`GlobalSettings` migration documentation|GlobalSettings]] for the gory details. ## `Plugin` deprecated -Both Java [`play.Plugin`](api/java/play/Plugin.html) and Scala [`play.api.Plugin`](api/scala/play/api/Plugin.html) types have been deprecated. Read [[migrating Plugin to Module|PluginsToModules]] to update your code to use [`play.api.inject.Module`](api/scala/play/api/inject/Module.html). +Both Java `play.Plugin` and Scala `play.api.Plugin` types have been deprecated. Read [[migrating Plugin to Module|PluginsToModules]] to update your code to use [`play.api.inject.Module`](api/scala/play/api/inject/Module.html). ## Configuration changes @@ -264,7 +267,7 @@ akka { } ``` -In particular, you might want to try the [default Akka settings](http://doc.akka.io/docs/akka/2.3.11/general/configuration.html#listing-of-the-reference-configuration): +In particular, you might want to try the [default Akka settings](https://doc.akka.io/docs/akka/2.3.11/general/configuration.html#listing-of-the-reference-configuration): ``` akka { @@ -292,7 +295,7 @@ play.http.requestHandler = "play.http.DefaultHttpRequestHandler" ### Logging -Logging is now configured solely via [logback configuration files](http://logback.qos.ch/manual/configuration.html). +Logging is now configured solely via [logback configuration files](https://logback.qos.ch/manual/configuration.html). ## JDBC connection pool @@ -342,7 +345,7 @@ Additionally, Java actions may now declare a `BodyParser.Of.maxLength` value tha ## JSON API changes -The semantics of JSON lookups have changed slightly. `JsUndefined` has been removed from the `JsValue` type hierarchy and all lookups of the form `jsv \ foo` or `jsv(bar)` have been moved to [`JsLookup`](api/scala/play/api/libs/json/JsLookup.html). They now return a [`JsLookupResult`](api/scala/play/api/libs/json/JsLookupResult.html) instead of a `JsValue`. +The semantics of JSON lookups have changed slightly. `JsUndefined` has been removed from the `JsValue` type hierarchy and all lookups of the form `jsv \ foo` or `jsv(bar)` have been moved to `JsLookup`. They now return a `JsLookupResult` instead of a `JsValue`. If you have code of the form @@ -356,7 +359,7 @@ the following code is equivalent, if you know the property exists: val v: JsValue = (json \ "foo" \ "bar").get ``` -If you don't know the property exists, we recommend using pattern matching or the methods on [`JsLookupResult`](api/scala/play/api/libs/json/JsLookupResult.html) to safely handle the `JsUndefined` case, e.g. +If you don't know the property exists, we recommend using pattern matching or the methods on `JsLookupResult` to safely handle the `JsUndefined` case, e.g. ```scala val vOpt: Option[JsValue] = (json \ "foo" \ "bar").toOption @@ -364,7 +367,7 @@ val vOpt: Option[JsValue] = (json \ "foo" \ "bar").toOption ### JsLookup -All JSON traversal methods have been moved to the [`JsLookup`](api/scala/play/api/libs/json/JsLookup.html) class, which is implicitly applied to all values of type `JsValue` or `JsLookupResult`. In addition to the `apply`, `\`, and `\\` methods, the `head`, `tail`, and `last` methods have been added for JSON arrays. All methods except `\\` return a [`JsLookupResult`](api/scala/play/api/libs/json/JsLookupResult.html), a wrapper for `JsValue` that helps with handling undefined values. +All JSON traversal methods have been moved to the `JsLookup` class, which is implicitly applied to all values of type `JsValue` or `JsLookupResult`. In addition to the `apply`, `\`, and `\\` methods, the `head`, `tail`, and `last` methods have been added for JSON arrays. All methods except `\\` return a `JsLookupResult`, a wrapper for `JsValue` that helps with handling undefined values. The methods `as[A]`, `asOpt[A]`, `validate[A]` also exist on `JsLookup`, so code like the below should require no source changes: @@ -405,23 +408,23 @@ implicit val optionStringReads: Reads[Option[String]] = Reads.optionWithNull[Str ## Testing changes -[`FakeRequest`](api/java/play/test/FakeRequest.html) has been replaced by [`RequestBuilder`](api/java/play/mvc/Http.RequestBuilder.html). +`FakeRequest` has been replaced by [`RequestBuilder`](api/java/play/mvc/Http.RequestBuilder.html). The reverse ref router used in Java tests has been removed. Any call to `Helpers.call` that was passed a ref router can be replaced by a call to `Helpers.route` which takes either a standard reverse router reference or a `RequestBuilder`. ## Java TimeoutExceptions -If you use the Java API, the [`F.Promise`](api/java/play/libs/F.Promise.html) class now throws unchecked [`F.PromiseTimeoutException`s](api/java/play/libs/F.PromiseTimeoutException.html) instead of Java's checked [`TimeoutException`s](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/TimeoutException.html). The `TimeoutExceptions`s which were previously used were not properly declared with the `throws` keyword. Rather than changing the API to use the `throws` keyword, which would mean users would have to declare `throws` on their methods, the exception was changed to a new unchecked type instead. See [#1227](https://github.com/playframework/playframework/pull/1227) for more information. +If you use the Java API, the `F.Promise` class now throws unchecked [`F.PromiseTimeoutException`s](api/java/play/libs/F.PromiseTimeoutException.html) instead of Java's checked [`TimeoutException`s](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/TimeoutException.html). The `TimeoutExceptions`s which were previously used were not properly declared with the `throws` keyword. Rather than changing the API to use the `throws` keyword, which would mean users would have to declare `throws` on their methods, the exception was changed to a new unchecked type instead. See [#1227](https://github.com/playframework/playframework/pull/1227) for more information. | Old API | New API | Comments | | ------- | --------| -------- | -| [`TimeoutException`](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/TimeoutException.html) | [`F.PromiseTimeoutException`](api/java/play/libs/F.PromiseTimeoutException.html) | | +| [`TimeoutException`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/TimeoutException.html) | [`F.PromiseTimeoutException`](api/java/play/libs/F.PromiseTimeoutException.html) | | ## WS client `WSRequestHolder` has been renamed to `WSRequest` in [Scala](api/scala/play/api/libs/ws/WSRequest.html) and [Java](api/java/play/libs/ws/WSRequest.html). The previous `WSRequest` class has been removed out as it was only used internally to WS for OAuth functionality. -WS has upgraded from AsyncHttpClient 1.8.x to 1.9.x, which includes a number of breaking changes if using or configuring that library directly. Please see the [AsyncHttpClient Migration Guide](https://github.com/AsyncHttpClient/async-http-client/blob/master/MIGRATION.md) for more details. The upgrade to AsyncHttpClient 1.9.x enables Server Name Indication (SNI) in HTTPS -- this solves a number of problems with HTTPS based CDNs such as Cloudflare which depend heavily on SNI. +WS has upgraded from AsyncHttpClient 1.8.x to 1.9.x, which includes a number of breaking changes if using or configuring that library directly. Please see the [AsyncHttpClient Migration Guide](https://github.com/AsyncHttpClient/async-http-client/blob/2.0/MIGRATION.md) for more details. The upgrade to AsyncHttpClient 1.9.x enables Server Name Indication (SNI) in HTTPS -- this solves a number of problems with HTTPS based CDNs such as Cloudflare which depend heavily on SNI. Configuration settings for WS have changed: @@ -450,9 +453,9 @@ Due to the recent spate of TLS vulnerabilities, there has been more activity to ## Crypto APIs -Play 2.4's AES encryption now uses [initialization vectors](http://en.wikipedia.org/wiki/Initialization_vector) to randomize each encryption. The Play encryption format has been changed to add support for initialization vectors. +Play 2.4's AES encryption now uses [initialization vectors](https://en.wikipedia.org/wiki/Initialization_vector) to randomize each encryption. The Play encryption format has been changed to add support for initialization vectors. -The full name of the new AES transformation used by Play 2.4 is `AES/CTR/NoPadding`. The old transformation was `AES/ECB/PKCS5Padding`. The [`CTR`](http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29) mode is much more secure than the `ECB` mode. As before, you can override Play's encryption transformation by setting the `play.crypto.aes.transformation` configuration option. In Play 2.4, any [transformation supported by your JRE](http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher) can be used, including transformations that use an initialization vector. +The full name of the new AES transformation used by Play 2.4 is `AES/CTR/NoPadding`. The old transformation was `AES/ECB/PKCS5Padding`. The [`CTR`](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29) mode is much more secure than the `ECB` mode. As before, you can override Play's encryption transformation by setting the `play.crypto.aes.transformation` configuration option. In Play 2.4, any [transformation supported by your JRE](https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher) can be used, including transformations that use an initialization vector. Play 2.4 uses a new encryption format, but it can read data encrypted by earlier versions of Play. However, earlier versions of Play **will not** be able to read data encrypted by Play 2.4. If your Play 2.4 application needs to produce data in the old format then you may want to copy the algorithm from the [Play 2.3 Crypto code](https://github.com/playframework/playframework/blob/2.3.6/framework/src/play/src/main/scala/play/api/libs/Crypto.scala#L187-L277). @@ -464,7 +467,7 @@ Old format | _hex(cipher(plaintext))_ | writes | reads | | reads New format I | "1-" + _base64(cipher(plaintext))_ | | | writes | reads New format II | "2-" + _base64(iv + cipher(plaintext, iv))_ | | | writes | reads -Usage of the [Java Crypto API](api/java/play/libs/Crypto.html) remains the same even though the output is different: +Usage of the Java Crypto API remains the same even though the output is different: ```java import play.libs.Crypto; @@ -473,7 +476,7 @@ String enc = Crypto.encryptAES(orig); String dec = Crypto.decryptAES(enc); ``` -Usage of the [Scala Crypto API](api/scala/play/api/libs/Crypto.html) is also the same: +Usage of the Scala Crypto API is also the same: ```scala import play.api.libs.Crypto @@ -535,7 +538,7 @@ The API should be backward compatible with your code using Play 2.3 so there is ## Distribution -Previously, Play added all the resources to the the `conf` directory in the distribution, but didn't add the `conf` directory to the classpath. Now Play adds the `conf` directory to the classpath by default. +Previously, Play added all the resources to the `conf` directory in the distribution, but didn't add the `conf` directory to the classpath. Now Play adds the `conf` directory to the classpath by default. This can be turned off by setting `PlayKeys.externalizeResources := false`, which will cause no `conf` directory to be created in the distribution, and it will not be on the classpath. The contents of the applications `conf` directory will still be on the classpath by virtue of the fact that it's included in the applications jar file. @@ -563,7 +566,7 @@ serverLoading in Debian := SystemV The mysterious `OrderedExecutionContext` had [[been retained|Migration22#Concurrent-F.Promise-execution]] in Play for several versions in order to support legacy applications. It was rarely used and has now been removed. If you still need the `OrderedExecutionContext` for some reason, you can create your own implementation based on the [Play 2.3 source](https://github.com/playframework/playframework/blob/2.3.x/framework/src/play/src/main/scala/play/core/j/OrderedExecutionContext.scala). If you haven't heard of this class, then there's nothing you need to do. -### SubProject Assets +### Subproject Assets Any assets in sub projects are now by default placed into /lib/[subproject] to allow files with the same name in the root project / different subprojects without causing them to interfere with each other. diff --git a/manual/releases/migration24/PluginsToModules.md b/manual/releases/release24/migration24/PluginsToModules.md similarity index 77% rename from manual/releases/migration24/PluginsToModules.md rename to manual/releases/release24/migration24/PluginsToModules.md index 2b79e7b9..dd03c28d 100644 --- a/manual/releases/migration24/PluginsToModules.md +++ b/manual/releases/release24/migration24/PluginsToModules.md @@ -1,7 +1,9 @@ - + # Migrating Plugin to Module -If you have implemented a Play plugin, please consider migrating your implementation to use [`play.api.inject.Module`](api/scala/play/api/inject/Module.html), instead of the deprecated Java [`play.Plugin`](api/java/play/Plugin.html) or Scala [`play.api.Plugin`](api/scala/play/api/Plugin.html) types. +> **Note:** The deprecated `play.Plugin` system is removed as of 2.5.x. + +If you have implemented a Play plugin, please consider migrating your implementation to use [`play.api.inject.Module`](api/scala/play/api/inject/Module.html), instead of the deprecated Java `play.Plugin` or Scala `play.api.Plugin` types. The main difference between the old `Plugin` API, and the new one using `Module`, is that with the latter we are going to fully embrace Dependency Injection (DI) - you can read [[here|Highlights24#Dependency-Injection]] to understand why Play became opinionated about DI. @@ -13,26 +15,25 @@ With the old `Plugin` API, you were required to provide a `play.plugins` file co Start by creating a class that inherits from `play.api.inject.Module`, and provide an implementation for the `bindings` method. In this method you should wire types to concrete implementation so that components provided by your module can be injected in users' code, or in other modules. Next follows an example. -In Java +In Java: @[module-decl](code24/MyModule.java) -In Scala +In Scala: @[module-decl](code24/MyModule.scala) - Note that if a component you are defining requires another component, you should simply add the required component as a constructor's dependency, prepending the constructor with the `@javax.inject.Inject` annotation. The DI framework will then take care of the rest. -> Note that if a component B requires A, then B will be initialized only after A is initialized. +> **Note:** if a component B requires A, then B will be initialized only after A is initialized. Next follows an example of a component named `MyComponentImpl` requiring the `ApplicationLifecycle` component. -In Java +In Java: @[components-decl](code24/MyComponent.java) -In Scala +In Scala: @[components-decl](code24/MyComponent.scala) @@ -45,8 +46,9 @@ play.modules.enabled += "my.module.MyModule" ``` If you are working on a library that will be used by other projects (including sub projects), add the above line in your `reference.conf` file (if you don't have a `reference.conf` yet, create one and place it under `src/main/resources`). Otherwise, if it's in an end Play project, it should be in `application.conf`. +If it's and end Play project, you could also create a class called `Module` and put it in the root package (the "app" directory). -> Note: If you are working on a library, it is highly discouraged to use `play.modules.disabled` to disable modules, as it can lead to undetermistic results when modules are loaded by the application (see [this issue](https://github.com/playframework/play-slick/issues/245) for reasons on why you should not touch `play.modules.disabled`). In fact, `play.modules.disabled` is intended for end users to be able to override what modules are enabled by default. +> **Note:** If you are working on a library, it is highly discouraged to use `play.modules.disabled` to disable modules, as it can lead to nondeterministic results when modules are loaded by the application (see [this issue](https://github.com/playframework/play-slick/issues/245) for reasons on why you should not touch `play.modules.disabled`). In fact, `play.modules.disabled` is intended for end users to be able to override what modules are enabled by default. ### Compile-time DI diff --git a/manual/releases/migration24/code24/MyComponent.java b/manual/releases/release24/migration24/code24/MyComponent.java similarity index 83% rename from manual/releases/migration24/code24/MyComponent.java rename to manual/releases/release24/migration24/code24/MyComponent.java index 0d984014..52fb21fd 100644 --- a/manual/releases/migration24/code24/MyComponent.java +++ b/manual/releases/release24/migration24/code24/MyComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ //#components-decl @@ -19,4 +19,4 @@ public MyComponentImpl(ApplicationLifecycle lifecycle) { }); } } -//#components-decl \ No newline at end of file +//#components-decl diff --git a/manual/releases/release24/migration24/code24/MyComponent.scala b/manual/releases/release24/migration24/code24/MyComponent.scala new file mode 100644 index 00000000..4a29b695 --- /dev/null +++ b/manual/releases/release24/migration24/code24/MyComponent.scala @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package scaladoc { + package mycomponent { + +//#components-decl + import javax.inject.Inject + import play.api.inject.ApplicationLifecycle + import scala.concurrent.Future + + trait MyComponent + + class MyComponentImpl @Inject()(lifecycle: ApplicationLifecycle) extends MyComponent { + // previous contents of Plugin.onStart + lifecycle.addStopHook { () => + // previous contents of Plugin.onStop + Future.successful(()) + } + } +//#components-decl + } +} diff --git a/manual/releases/migration24/code24/MyModule.java b/manual/releases/release24/migration24/code24/MyModule.java similarity index 82% rename from manual/releases/migration24/code24/MyModule.java rename to manual/releases/release24/migration24/code24/MyModule.java index ce0c6a16..1f8dbbe2 100644 --- a/manual/releases/migration24/code24/MyModule.java +++ b/manual/releases/release24/migration24/code24/MyModule.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ //#module-decl @@ -17,4 +17,4 @@ public Seq> bindings(Environment environment, Configuration configura ); } } -//#module-decl \ No newline at end of file +//#module-decl diff --git a/manual/releases/release24/migration24/code24/MyModule.scala b/manual/releases/release24/migration24/code24/MyModule.scala new file mode 100644 index 00000000..56753e01 --- /dev/null +++ b/manual/releases/release24/migration24/code24/MyModule.scala @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package scaladoc { + package module { + + import mycomponent._ +//#module-decl + import play.api.Configuration + import play.api.Environment + import play.api.inject.Binding + import play.api.inject.Module + + class MyModule extends Module { + def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = { + Seq( + bind[MyComponent].to[MyComponentImpl] + ) + } + } +//#module-decl + +//#components-decl + import play.api.inject.ApplicationLifecycle + + trait MyComponents { + def applicationLifecycle: ApplicationLifecycle + lazy val component: MyComponent = new MyComponentImpl(applicationLifecycle) + } +//#components-decl + } +} diff --git a/manual/releases/release24/migration24/index.toc b/manual/releases/release24/migration24/index.toc new file mode 100644 index 00000000..567118fc --- /dev/null +++ b/manual/releases/release24/migration24/index.toc @@ -0,0 +1,4 @@ +Migration24:Migration Guide +GlobalSettings:Removing `GlobalSettings` +Anorm:Migrating Anorm +PluginsToModules:Migrating Plugin to Module \ No newline at end of file diff --git a/manual/releases/release25/Highlights25.md b/manual/releases/release25/Highlights25.md new file mode 100644 index 00000000..e722db6d --- /dev/null +++ b/manual/releases/release25/Highlights25.md @@ -0,0 +1,93 @@ + +# What's new in Play 2.5 + +This page highlights the new features of Play 2.5. If you want to learn about the changes you need to make to migrate to Play 2.5, check out the [[Play 2.5 Migration Guide|Migration25]]. + +## New streaming API based on Akka Streams + +The main theme of Play 2.5 has been moving from Play's iteratee-based asynchronous IO API to [Akka Streams](https://doc.akka.io/docs/akka/2.4.3/scala/stream/stream-introduction.html). + +At its heart, any time you communicate over the network, or write/read some data to the filesystem, some streaming is involved. In many cases, this streaming is done at a low level, and the framework exposes the materialized values to your application as in-memory messages. This is the case for many Play actions, a body parser converts the request body stream into an object such as a parsed JSON object, which the application consumes, and the returned result body is a JSON object that Play then turns back into a stream. + +Traditionally, on the JVM, streaming is done using the blocking `InputStream` and `OutputStream` APIs. These APIs require a dedicated thread to use them - when reading, that thread must block and wait for data, when writing, that thread must block and wait for the data to be flushed. An asynchronous framework, such as Play, offers many advantages because it limits the resources it requires by not using blocking APIs such as these. Instead, for streaming, an asynchronous API needs to be used, where the framework is notified that there's data to read or that data has been written, rather than having to have a thread block and wait for it. + +Prior to Play 2.5, Play used Iteratees as this asynchronous streaming mechanism, but now it uses Akka Streams. + +### Why not iteratees? + +Iteratees are a functional approach to handling asynchronous streaming. They are incredibly powerful, while also offering an incredibly small API surface area - the Iteratee API consists of one method, `fold`, the rest is just helpers built on top of this method. Iteratees also provide a very high degree of safety, as long as your code compiles, it's very unlikely that you would have any bugs related to the implementation of an iteratee itself, such as concurrency or error handling, most bugs would be in the "business" logic of the iteratee. + +While this safety and simplicity is great, the consequence of it was that it has a very steep learning curve. Programming using iteratees requires a shift in thinking from traditional IO handling, and many developers find that the investment required to make this shift is too high for their IO needs. Another disadvantage of iteratees is that they are practically unimplementable in Java, due to their reliance on many high level functional programming features. + +### Why Akka Streams + +Akka Streams provides a good balance between safety, simplicity and familiarity. Akka Streams intentionally constrains you in what you can do so that you can only do things correctly, but not as much as iteratees do. Conceptually they are much more familiar to most developers, offering both functional and imperative ways of working with them. Akka Streams also has a first class Java API, making it simple to implement any streaming requirements in Java that are needed. + +### Where are Akka Streams used? + +The places where you will come across Akka Streams in your Play applications include: + +* Filters +* Streaming response bodies +* Request body parsers +* WebSockets +* Streaming WS client responses + +### Reactive Streams + +[Reactive Streams](http://reactivestreams.org) is a new specification for asynchronous streaming, which is scheduled for inclusion in JDK9 and available as a standalone library for JDK6 and above. In general, it is not an end-user library, rather it is an SPI that streaming libraries can implement in order to integrate with each other. Both Akka Streams and iteratees provide a reactive streams SPI implementation. This means, existing iteratees code can easily be used with Play's new Akka Streams support. It also means any other reactive streams implementations can be used in Play. + +### The future of iteratees + +Iteratees still have some use cases where they shine. At current, there is no plan to deprecate or remove them from Play, though they may be moved to a standalone library. Since iteratees provide a reactive streams implementation, they will always be usable in Play. + +## Better control over WebSocket frames + +The Play 2.5 WebSocket API gives you direct control over WebSocket frames. You can now send and receive binary, text, ping, pong and close frames. If you don't want to worry about this level of detail, Play will still automatically convert your JSON or XML data into the right kind of frame. + +## New Java APIs + +Play 2.5's Java APIs provide feature parity with the Scala APIs. We have introduced several new Java APIs to do this: + +* [`HttpRequestHandler`](api/java/play/http/HttpRequestHandler.html), which allows interception of requests as they come in, before they are sent to the router. +* [`EssentialAction`](api/java/play/mvc/EssentialAction.html), a low level action used in `EssentialFilter` and `HttpRequestHandler`s. +* [`EssentialFilter`](api/java/play/mvc/EssentialFilter.html)/[`Filter`](api/java/play/mvc/Filter.html) for writing filters in Java. +* [`BodyParser`](api/java/play/mvc/BodyParser.html), which allows writing custom body parsers in Java. + +## Java API updated to use Java 8 classes + +When Play 2.0 was released in 2012, Java had little support for Play's style of asynchronous functional programming. There were no lambdas, futures only had a blocking interface and common functional classes didn't exist. Play provided its own classes to fill the gap. + +With Play 2.5 that situation has changed. Java 8 now ships with much better support for Play's style of programming. In Play 2.5 the Java APIs have been revamped to use standard Java 8 classes. This means that Play applications will integrate better with other Java libraries and look more like idiomatic Java. + +Here are the main changes: + +* Use Java functional interfaces (`Runnable`, `Consumer`, `Predicate`, etc). [[(See Migration Guide.)|JavaMigration25#Replaced-functional-types-with-Java-8-functional-types]] +* Use Java 8's [`Optional`](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html) instead of Play's `F.Option`. [[(See Migration Guide.)|JavaMigration25#Replaced-F.Option-with-Java-8s-Optional]] +* Use Java 8's [`CompletionStage`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html) instead of Play's `F.Promise`. [[(See Migration Guide.)|JavaMigration25#Replaced-F.Promise-with-Java-8s-CompletionStage]] + +## Support for other logging frameworks + +Many of our users want to use their own choice of logging framework but this was not possible until Play 2.5. Now Play's fixed dependency on [Logback](https://logback.qos.ch/) has been removed and Play applications can now use any [SLF4J](https://www.slf4j.org/)-compatible logging framework. Logback is included by default, but you can disable it by including a setting in your `build.sbt` file and replace it with your own choice of framework. See Play's [[docs about logging|SettingsLogger#Using-a-Custom-Logging-Framework]] for more information about using other logging frameworks in Play. + +Play applications will need to make a small change to their configuration because one Play's Logback classes has moved to a separate package as part of the change. See the [[Migration Guide|Migration25#Change-to-Logback-configuration]] for more details. + +## Logging SQL statements + +Play now has an easy way to log SQL statements, built on [jdbcdslog](https://github.com/jdbcdslog/jdbcdslog), that works across all JDBC databases, connection pool implementations and persistence frameworks (Anorm, Ebean, JPA, Slick, etc). When you enable logging you will see each SQL statement sent to your database as well as performance information about how long the statement takes to run. + +For more information about how to use SQL logging, see the Play [[Java|JavaDatabase#How-to-configure-SQL-log-statement]] and [[Scala|ScalaDatabase#How-to-configure-SQL-log-statement]] database documentation. + +## Netty native socket transport + +If you run Play server on Linux you can now get a performance boost by using the [native socket feature](https://netty.io/wiki/native-transports.html) that was introduced in Netty 4.0. + +You can learn how to use native sockets in Play documentation on [[configuring Netty|SettingsNetty#Configuring-transport-socket]]. + +## Performance Improvements + +Thanks to various performance optimizations, Play 2.5's performance testing framework shows roughly 60K requests per second, an **almost 20% improvement over Play 2.4.x**. + +## WS Improvements + +Play WS has been upgraded to AsyncHttpClient 2.0, and now includes a request pipeline filter ([[Scala|ScalaWS#Request-Filters]], [[Java|JavaWS#Request-Filters]]) that can be used to log requests in [cURL format](https://curl.haxx.se/docs/manpage.html). diff --git a/manual/releases/release25/index.toc b/manual/releases/release25/index.toc new file mode 100644 index 00000000..a72269a4 --- /dev/null +++ b/manual/releases/release25/index.toc @@ -0,0 +1,2 @@ +Highlights25:What's new? +!migration25:Migration Guides diff --git a/manual/releases/release25/migration25/CryptoMigration25.md b/manual/releases/release25/migration25/CryptoMigration25.md new file mode 100644 index 00000000..fe5914c4 --- /dev/null +++ b/manual/releases/release25/migration25/CryptoMigration25.md @@ -0,0 +1,98 @@ + +# Crypto Migration Guide + +From Play 1.x, Play has come with a Crypto object that provides some cryptographic operations. This used internally by Play. The Crypto object is not mentioned in the documentation, but is mentioned as "cryptographic utilities" in the scaladoc: + +"These utilities are intended as a convenience, however it is important to read each methods documentation and understand the concepts behind encryption to use this class properly. Safe encryption is hard, and there is no substitute for an adequate understanding of cryptography. These methods will not be suitable for all encryption needs." + +For a variety of reasons, providing cryptographic utilities as a convenience has turned out not to be workable. In 2.5.x, the Play-specific functionality has been broken into `CookieSigner`, `CSRFTokenSigner` and `AESSigner` traits, and the `Crypto` singleton object deprecated. + +The remainder of this document will discuss internal functionality behind Crypto, the suitability (and unsuitability) of cryptographic operations, and migration paths for users moving off Crypto functionality. + +For more information about cryptography, we recommend reading the [OWASP Cryptographic Storage Cheatsheet](https://www.owasp.org/index.php/Cryptographic_Storage_Cheat_Sheet). + +## Message Authentication + +Play uses the `Crypto.sign` method to provide message authentication for session cookies. There are several reasons why `Crypto.sign` should not be used outside the purpose of signing cookies: MAC algorithm independence, additional functionality, and potential misuse of HMAC as a password hashing algorithm. + +### MAC Algorithm Independence + +Play currently uses HMAC-SHA1 for signing and verifying session cookies. An [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) is a cryptographic function that authenticates that data has not been tampered with, using a secret key (the [[application secret|ApplicationSecret]] defined as play.crypto.secret) together with a message digest function (in this case [SHA-1](https://en.wikipedia.org/wiki/SHA-1)). SHA-1 has suffered [some attacks recently](https://sites.google.com/site/itstheshappening/), but it remains secure when used with an HMAC for [message authenticity](https://www.killring.org/how-broken-is-sha-1). + +Play needs to have the flexibility be able to move to a different HMAC function [as needed](http://valerieaurora.org/hash.html) and so, should not be part of the public API. + +### Potential New Functionality + +Play currently signs the session cookie, but does not add any session timeout or expiration date to the session cookie. This means that, given the appropriate opening, an active attacker could swap out one session cookie for another one. + +Play may potentially add [session timeout functionality](https://github.com/google/keyczar/blob/master/java/code/src/org/keyczar/TimeoutSigner.java#L109) to Crypto.sign, which again would result in breaking user level functionality if this is marked as public API. + +### Misuse as a Password Hash + +Please do not use `Crypto.sign` or any kind of HMAC, as they are not designed for password hashing. MACs are designed to be fast and cheap, while password hashing should be slow and expensive. Please look at scrypt, bcrypt, or PBKDF2 -- [jBCrypt](http://www.mindrot.org/projects/jBCrypt/) in particular is well known as a bcrypt implementation in Java. + +## Symmetric Encryption + +Crypto contains two methods for symmetric encryption, `Crypto.encryptAES` and `Crypto.decryptAES`. These methods are not used internally by Play, but [significant](https://github.com/playframework/playframework/issues/4407) [developer](https://groups.google.com/d/msg/play-framework-dev/Rlrt89Ky_Rk/j6Iq6-snDw8J) [effort](https://groups.google.com/forum/#!topic/play-framework/Pao8MnADAqw) [has](https://ipsec.pl/play-framework/2014/session-variables-encryption-play-framework.html) gone into reviewing these methods. These methods will be deprecated, and may be removed in future versions. + +As alluded to in the warning, these methods are not generally "safe" -- there are some common modes of operation that are not secure using these methods. Here follows a brief description of some cryptographic issues using `Crypto.encryptAES`. + +Again, `Crypto.encryptAES` is never used directly in Play, so this isn’t a security vulnerability in Play itself. + +### Use of Stream Cipher without Authentication + +`Crypto.encryptAES` is, by default, using AES-CTR, a mode of AES that provides encryption but does not provide authentication -- this means that an active attacker can swap out the contents of the encrypted text with something else. This property, known as "malleability", means that it’s possible to [recover plaintext](https://news.ycombinator.com/item?id=639761) under certain conditions, and alter the message. There are two constructions that are used to mitigate this: either the encrypted text is signed (known as “Encrypt Then MAC”) or authenticated encryption, such as AES-GCM, can be used. + +Play uses `Crypto.encryptAES` to encrypt the contents of a session cookie (which has a MAC applied). Since Play uses "Encrypt Then MAC", it is not vulnerable to attack -- however, users who are making use of symmetric encryption without a MAC are potentially vulnerable. + +Users are encouraged not to implement their own Encrypt-Then-MAC construction. Instead, the appropriate solution here is authenticated encryption, which both authenticates and encrypts data at the same time -- see the migration section for details. + +### Violation of Key Separation Principle + +Although using AES with an HMAC is considered a secure construction, there is a problem in the way that Play uses the secret key for encryption. The "key separation principle" says that you should only ever use one key for one purpose. In this case, not only is play.crypto.secret is used for signing, but also encryption in `Crypto.encryptAES`. + +For customers who are using `Crypto.encryptAES`, there is no immediate security vulnerability that results from the mixing of key usage here: + +"With HMAC vs AES, no such interference is known. The *general feeling* of cryptographers is that AES and SHA-1 (or SHA-256) are "sufficiently different" that there should be no practical issue with using the same key for AES and HMAC/SHA-1." -- . + +Once the application gets larger, the key separation principle might be also violated in another way: If `Crypto.encryptAES` is used for multiple purposes, using separate keys is also advised. + +### Global configuration of mode + +As mentioned above, AES can be used with various modes of operation. Using a different mode might require different additional security measures. + +Play, however, offers configuring mode of operation globally by configuring the play.crypto.aes.transformation configuration option. That is, it influences whole application, including all libraries that use `Crypto.encryptAES`. As a result, it is hard to know exactly what the impact of changing the option on the whole application. + +## Migration + +There are several migration paths from Crypto functionality. In order of preference, they are Kalium, Keyczar, or pure JCA. + +### Kalium + +If you have control over binaries in your production environment and do not have external requirements for NIST approved algorithms: use [Kalium](https://abstractj.github.io/kalium/), a wrapper over the [libsodium](https://download.libsodium.org/doc/) library. + +If you need a MAC replacement for `Crypto.sign`, use `org.abstractj.kalium.keys.AuthenticationKey`, which implements HMAC-SHA512/256. + +If you want a symmetric encryption replacement for `Crypto.encryptAES`, then use `org.abstractj.kalium.crypto.SecretBox`, which implements [secret-key authenticated encryption](https://download.libsodium.org/doc/secret-key_cryptography/authenticated_encryption.html). + +Note that Kalium does require that a libsodium binary be [installed](https://download.libsodium.org/doc/installation/index.html), preferably from source that you have verified. + +### Keyczar + +If you are looking for a pure Java solution or depend on NIST approved algorithms, [Keyczar](https://tersesystems.com/2015/10/05/effective-cryptography-in-the-jvm/) provides a high level cryptographic library on top of JCA. Note that Keyczar does not have the same level of support as libsodium / Kalium, and so Kalium is preferred. + +If you need a MAC replacement for `Crypto.sign`, use `org.keyczar.Signer`. + +If you need a symmetric encryption replacement for `Crypto.encryptAES`, then use `org.keyczar.Crypter`. + +### JCA + +Both Kalium and Keyczar use different cryptographic primitives than Crypto. For users who intend to migrate from Crypto functionality without changing the underlying algorithms, the best option is probably to extract the code from the Crypto library to a user level class. + +### Further Reading + +There are some papers available on cryptographic design that go over some of the issues addressed by crypto APIs and the complexities involved: + +* [The Long Journey from Papers to Software: Crypto APIs](https://crypto.junod.info/IACR15_crypto_school_talk.pdf) +* [What’s Wrong with Crypto API Design](http://spar.isi.jhu.edu/~mgreen/CryptoAPIs.pdf) +* [Real World Crypto 2015: Error-prone cryptographic designs (djb)](http://bristolcrypto.blogspot.com/2015/01/real-world-crypto-2015-error-prone.html) and [slides](http://cr.yp.to/talks/2015.01.07/slides-djb-20150107-a4.pdf) diff --git a/manual/releases/release25/migration25/JavaMigration25.md b/manual/releases/release25/migration25/JavaMigration25.md new file mode 100644 index 00000000..d95babba --- /dev/null +++ b/manual/releases/release25/migration25/JavaMigration25.md @@ -0,0 +1,176 @@ + +# Java Migration Guide + +In order to better fit in to the Java 8 ecosystem, and to allow Play Java users to make more idiomatic use of Java in their applications, Play has switched to using a number of Java 8 types such as `CompletionStage` and `Function`. Play also has new Java APIs for `EssentialAction`, `EssentialFilter`, `Router`, `BodyParser` and `HttpRequestHandler`. + +## New Java APIs + +There are several API changes to accommodate writing filters and HTTP request handlers in Java, in particular with the `HttpRequestHandler` interface. If you are using Scala for these components you are still free to use the Scala API if you wish. + +### Filter API + +You will most likely use `EssentialAction` when creating a filter. You can either use the [`Filter`](api/java/play/mvc/Filter.html) API or the lower-level [`EssentialFilter`](api/java/play/mvc/EssentialFilter.html) API that operates on [`EssentialAction`](api/java/play/mvc/EssentialAction.html)s. + +### HttpRequestHandler and ActionCreator + +The [`HttpRequestHandler`](api/java/play/http/HttpRequestHandler.html) actually existed in Play 2.4, but now it serves a different purpose. The `createAction` and `wrapAction` methods have been moved to a new interface called [`ActionCreator`](api/java/play/http/ActionCreator.html), and are deprecated in `HttpRequestHandler`. These methods are only applied to Java actions, and are used to intercept requests to the controller's method call, but not all requests. + +In 2.5, `HttpRequestHandler`'s main purpose is to provide a handler for the request right after it comes in. This is now consistent with what the Scala implementation does, and provides a way for Java users to intercept the handling of all HTTP requests. Normally, the `HttpRequestHandler` will call the router to find an action for the request, so the new API allows you to intercept that request in Java before it goes to the router. + +## Using CompletionStage inside an Action + +You must supply the HTTP execution context explicitly as an executor when using a Java `CompletionStage` inside an [[Action|JavaActions]], to ensure that the HTTP.Context remains in scope. If you don't supply the HTTP execution context, you'll get "There is no HTTP Context available from here" errors when you call `request()` or other methods that depend on `Http.Context`. + +You can supply the [`play.libs.concurrent.HttpExecutionContext`](api/java/play/libs/concurrent/HttpExecutionContext.html) instance through dependency injection: + +``` java +public class Application extends Controller { + @Inject HttpExecutionContext ec; + + public CompletionStage index() { + someCompletableFuture.supplyAsync(() -> { + // do something with request() + }, ec.current()); + } +} +``` + +## Replaced functional types with Java 8 functional types + +A big change in Play 2.5 is the change to use standard Java 8 classes where possible. All functional types have been replaced with their Java 8 counterparts, for example `F.Function1` has been replaced with `java.util.function.Function`. + +The move to Java 8 types should enable better integration with other Java libraries as well as with built-in functionality in Java 8. + +### How to migrate + +**Step 1:** Change all code that references Play functional interfaces to reference Java 8 interfaces instead. + +You need to change code that explicitly mentions a type like `F.Function1`. For example: + +```java +void myMethod(F.Callback0 block) { ... } +``` + +Becomes: + +```java +void myMethod(Runnable block) { ... } +``` + +The table below shows all the changes: + +| **old interface** | **new interface** +| -------------------------------------- +| `F.Callback0` | `java.lang.Runnable` +| `F.Callback` | `java.util.function.Consumer` +| `F.Callback2` | `java.util.function.BiConsumer` +| `F.Callback3` | No counterpart in Java 8, consider using `akka.japi.function.Function3` +| `F.Predicate` | `java.util.function.Predicate` +| `F.Function0` | `java.util.function.Supplier` +| `F.Function1` | `java.util.function.Function` +| `F.Function2` | `java.util.function.BiFunction` + +**Step 2:** Fix any errors caused by checked exceptions that are thrown inside your lambdas. + +Unlike the Play functional interfaces, the Java 8 functional interfaces don't permit checked exceptions to be thrown. If your lambda expressions throw a checked exception then you'll need to change the code. (If you don't throw checked exceptions then you can leave the code unchanged.) + +You may get a lot of compiler errors but it's pretty easy to get your code working again. Let's suppose your Play 2.4 code uses a `F.Callback0` lambda to stop a database: + +```java +onClose(() -> { + database.stop(); // <-- can throw an IOException +}) +``` + +In Play 2.5 the `onClose` method has been changed to take a `java.lang.Runnable` argument instead of `F.Callback0`. Because `Runnable`s can't throw checked exceptions the code above won't compile in Play 2.5. + +To get the code to compile you can change your lambda code to catch the checked exception (`IOException`) and wrap it in an unchecked exception (`RuntimeException`). It's OK for a `Runnable` to throw an unchecked exception so the code will now compile. + +```java +onClose(() -> { + try { + database.stop(); // <-- can throw an IOException + } catch (IOException e) { + throw new RuntimeException(e); + } +}) +``` + +If you don't like adding *try-catch* blocks into your code you can use the [Durian](https://github.com/diffplug/durian) library's [`Errors`](https://diffplug.github.io/durian/javadoc/2.0/com/diffplug/common/base/Errors.html) class to handle exceptions for you. + +For example, you can get the same behavior as above, where you convert a checked exception into an unchecked exception, with the code below: + +```java +onClose(Errors.rethrow().wrap(database::stop)); +``` + +Durian provides other behaviors too, such as [logging an exception](https://diffplug.github.io/durian/javadoc/2.0/com/diffplug/common/base/Errors.html#log--) or writing [your own exception handler](https://diffplug.github.io/durian/javadoc/2.0/com/diffplug/common/base/Errors.html#createHandling-java.util.function.Consumer-). If you want to use Durian you can either include it as a [dependency in your project](https://mvnrepository.com/artifact/com.diffplug.durian/durian) or by copy the source from [two](https://github.com/diffplug/durian/blob/master/src/com/diffplug/common/base/Errors.java) [classes](https://github.com/diffplug/durian/blob/master/src/com/diffplug/common/base/Throwing.java) into your project. + +## Replaced `F.Promise` with Java 8's `CompletionStage` + +APIs that use `F.Promise` now use the standard Java 8 [`CompletionStage`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html) class. + +### How to migrate + +**Step 1:** Change all code that returns `F.Promise` to return `CompletionStage` instead. To aid with migration, `F.Promise` also implements the `CompletionStage` interface, which means any existing code that returns `Promise` can still be invoked from code that has been migrated to use `CompletionStage`. + +**Step 2:** Replace relevant static methods in `F.Promise` with an equivalent method (many of these use the [`play.libs.concurrent.Futures`](api/java/play/libs/concurrent/Futures.html) helpers, or the statics on [`CompletableFuture`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html)): + +| `F.Promise` method | alternative | +| --------------------------------------------| +| `Promise.wrap` | `scala.compat.java8.FutureConverters.toJava` | +| `Promise.sequence` | `Futures.sequence` | +| `Promise.timeout` | `Futures.timeout` | +| `Promise.pure` | `CompletableFuture.completedFuture` | +| `Promise.throwing` | Construct `CompletableFuture` and use `completeExceptionally` | +| `Promise.promise` | `CompletableFuture.supplyAsync` | +| `Promise.delayed` | `Futures.delayed` | + +**Step 3:** Replace existing instance methods with their equivalent on `CompletionStage`: + +| `F.Promise` | `CompletionStage` | +| -----------------------------------------------| +| `or` | `applyToEither` | +| `onRedeem` | `thenAcceptAsync` | +| `map` | `thenApplyAsync` | +| `transform` | `handleAsync` | +| `zip` | `thenCombine` (and manually construct a tuple) | +| `fallbackTo` | `handleAsync` followed by `thenCompose(Function.identity())` | +| `recover` | `exceptionally` (or `handleAsync` with `HttpExecution#defaultContext()` if you want `Http.Context` captured). | +| `recoverWith` | same as `recover`, then use `.thenCompose(Function.identity())` | +| `onFailure` | `whenCompleteAsync` (use `HttpExecution#defaultContext()` if needed) | +| `flatMap` | `thenComposeAsync` (use `HttpExecution#defaultContext()` if needed) | +| `filter` | `thenApplyAsync` and implement the filter manually (use `HttpExecution#defaultContext()` if needed) | + +These migrations are explained in more detail in the Javadoc for `F.Promise`. + +## Replaced `F.Option` with Java 8's `Optional` + +The Play Java API has been converted to use the Java 8 [`Optional`](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html) class instead of Play's `F.Option` type. The `F.Option` type has been removed. + +### How to migrate + +Replace code that uses `F.Option` with `Optional`. The two types are similar, but their API is different, so you will need to update your code. The main difference between the two types is that while `F.Option` inherits `java.util.Collection`, `Optional` doesn't. + +Here follows a short table that should ease the migration: + +| `F.Option` | `Optional` | +| ---------- | ---------- | +| `F.Option.None()` | `Optional.empty()` | +| `F.Option.Some(v)` | `Optional.ofNullable(v)` | +| `o.isDefined()` | `o.isPresent()` | +| `o.isEmpty()` | `!o.isPresent()` | +| `o.get()` | `o.get()` | +| `o.getOrElse(f)` | `o.orElseGet(f)` or `o.orElse(v)` | +| `o.map(f)` | `o.map(f)` | + +`Optional` has a lot more combinators, so we highly encourage you to [learn its API](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html) if you are not familiar with it already. + +## Thread Local attributes + +Thread Local attributes such as `Http.Context`, `Http.Session` etc are no longer passed to a different execution context when used with `CompletionStage` and `*Async` callbacks. +More information is [here](https://www.playframework.com/documentation/2.5.x/ThreadPools#Java-thread-locals) + +## Deprecated static APIs + +Several static APIs were deprecated in Play 2.5, in favour of using dependency injected components. Using static global state is bad for testability and modularity, and it is recommended that you move to dependency injection for accessing these APIs. You should refer to the list in the [[Play 2.4 Migration Guide|Migration24#Dependency-Injected-Components]] to find the equivalent dependency injected component for your static API. diff --git a/manual/releases/release25/migration25/Migration25.md b/manual/releases/release25/migration25/Migration25.md new file mode 100644 index 00000000..6d488fdf --- /dev/null +++ b/manual/releases/release25/migration25/Migration25.md @@ -0,0 +1,352 @@ + +# Play 2.5 Migration Guide + +This is a guide for migrating from Play 2.4 to Play 2.5. If you need to migrate from an earlier version of Play then you must first follow the [[Play 2.4 Migration Guide|Migration24]]. + +As well as the information contained on this page, there is more detailed migration information for some topics: + +- [[Streams Migration Guide|StreamsMigration25]] – Migrating to Akka Streams, now used in place of iteratees in many Play APIs +- [[Java Migration Guide|JavaMigration25]] - Migrating Java applications. Play now uses native Java types for functional types and offers several new customizable components in Java. + +Lucidchart has also put together an informative blog post on [upgrading from Play 2.3.x to Play 2.5.x](https://www.lucidchart.com/techblog/2017/02/22/upgrading-play-framework-2-3-play-2-5/). + +## How to migrate + +The following steps need to be taken to update your sbt build before you can load/run a Play project in sbt. + +### Play upgrade + +Update the Play version number in project/plugins.sbt to upgrade Play: + +```scala +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.x") +``` + +Where the "x" in `2.5.x` is the minor version of Play you want to use, per instance `2.5.0`. + +### sbt upgrade to 0.13.11 + +Although Play 2.5 will still work with sbt 0.13.8, we recommend upgrading to the latest sbt version, 0.13.11. The 0.13.11 release of sbt has a number of [improvements and bug fixes](https://github.com/sbt/sbt/releases/tag/v0.13.11). + +Update your `project/build.properties` so that it reads: + +``` +sbt.version=0.13.11 +``` + +### Play Slick upgrade + +If your project is using Play Slick, you need to upgrade it: + +```scala +libraryDependencies += "com.typesafe.play" %% "play-slick" % "2.0.0" +``` + +Or: + +```scala +libraryDependencies ++= Seq( + "com.typesafe.play" %% "play-slick" % "2.0.0", + "com.typesafe.play" %% "play-slick-evolutions" % "2.0.0" +) +``` + +### Play Ebean upgrade + +If your project is using Play Ebean, you need to upgrade it: + +```scala +addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "3.0.0") +``` + +### ScalaTest + Plus upgrade + +If your project is using [[ScalaTest + Play|ScalaTestingWithScalaTest]], you need to upgrade it: + +```scala +libraryDependencies ++= Seq( + "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % "test" +) +``` + +## Scala 2.10 support discontinued + +Play 2.3 and 2.4 supported both Scala 2.10 and 2.11. Play 2.5 has dropped support for Scala 2.10 and now only supports Scala 2.11. There are a couple of reasons for this: + +1. Play 2.5's internal code makes extensive use of the [scala-java8-compat](https://github.com/scala/scala-java8-compat) library, which only supports Scala 2.11. The *scala-java8-compat* has conversions between many Scala and Java 8 types, such as Scala `Future`s and Java `CompletionStage`s. (You might find this library useful for your code too.) + +2. The next version of Play will probably add support for Scala 2.12. It's time for Play to move to Scala 2.11 so that the upcoming transition to 2.12 will be easier. + +### How to migrate + +**Both Scala and Java users** must configure sbt to use Scala 2.11. Even if you have no Scala code in your project, Play itself uses Scala and must be configured to use the right Scala libraries. + +To set the Scala version in sbt, simply set the `scalaVersion` key, eg: + +```scala +scalaVersion := "2.11.8" +``` + +If you have a single project build, then this setting can just be placed on its own line in `build.sbt`. However, if you have a multi project build, then the scala version setting must be set on each project. Typically, in a multi project build, you will have some common settings shared by every project, this is the best place to put the setting, eg: + +```scala +def common = Seq( + scalaVersion := "2.11.8" +) + +lazy val projectA = (project in file("projectA")) + .enablePlugins(PlayJava) + .settings(common: _*) + +lazy val projectB = (project in file("projectB")) + .enablePlugins(PlayJava) + .settings(common: _*) +``` + +## Change to Logback configuration + +As part of the change to remove Play's hardcoded dependency on Logback [[(see Highlights)|Highlights25#Support-for-other-logging-frameworks]], one of the classes used by Logback configuration had to be moved to another package. + +### How to migrate + +You will need to update your Logback configuration files (`logback*.xml`) and change any references to the old `play.api.Logger$ColoredLevel` to the new `play.api.libs.logback.ColoredLevel` class. + +The new configuration after the change will look something like this: + +```xml + +``` + +If you use compile time dependency injection, you will need to change your application loader from using `Logger.configure(...)` to the following: + +```scala +LoggerConfigurator(context.environment.classLoader).foreach { _.configure(context.environment) } +``` + +You can find more details on how to set up Play with different logging frameworks are in [[Configuring logging|SettingsLogger#Using-a-Custom-Logging-Framework]] section of the documentation. + +## Play WS upgrades to AsyncHttpClient 2 + +Play WS has been upgraded to use [AsyncHttpClient 2](https://github.com/AsyncHttpClient/async-http-client). This is a major upgrade that uses Netty 4.0. Most of the changes in AHC 2.0 are under the hood, but AHC has some significant refactorings which require breaking changes to the WS API: + +* `AsyncHttpClientConfig` replaced by [`DefaultAsyncHttpClientConfig`](https://static.javadoc.io/org.asynchttpclient/async-http-client/2.0.0/org/asynchttpclient/DefaultAsyncHttpClientConfig.html). +* [`allowPoolingConnection`](https://static.javadoc.io/com.ning/async-http-client/1.9.32/com/ning/http/client/AsyncHttpClientConfig.html#allowPoolingConnections) and `allowSslConnectionPool` are combined in AsyncHttpClient into a single `keepAlive` variable. As such, `play.ws.ning.allowPoolingConnection` and `play.ws.ning.allowSslConnectionPool` are not valid and will throw an exception if configured. +* [`webSocketIdleTimeout`](https://static.javadoc.io/com.ning/async-http-client/1.9.32/com/ning/http/client/AsyncHttpClientConfig.html#webSocketTimeout) has been removed, so is no longer available in `AhcWSClientConfig`. +* [`ioThreadMultiplier`](https://static.javadoc.io/com.ning/async-http-client/1.9.32/com/ning/http/client/AsyncHttpClientConfig.html#ioThreadMultiplier) has been removed, so is no longer available in `AhcWSClientConfig`. +* [`FluentCaseInsensitiveStringsMap`](https://static.javadoc.io/com.ning/async-http-client/1.9.32/com/ning/http/client/FluentCaseInsensitiveStringsMap.html) class is removed and replaced by Netty's `HttpHeader` class. +* [`Realm.AuthScheme.None`](https://static.javadoc.io/com.ning/async-http-client/1.9.32/com/ning/http/client/Realm.AuthScheme.html#NONE) has been removed, so is no longer available in `WSAuthScheme`. + +In addition, there are number of small changes: + +* In order to reflect the proper AsyncHttpClient library name, package `play.api.libs.ws.ning` was renamed into `play.api.libs.ws.ahc` and `Ning*` classes were renamed into `Ahc*`. In addition, the AHC configuration settings have been changed to `play.ws.ahc` prefix, i.e. `play.ws.ning.maxConnectionsPerHost` is now `play.ws.ahc.maxConnectionsPerHost`. +* The deprecated interface `play.libs.ws.WSRequestHolder` has been removed. +* The `play.libs.ws.play.WSRequest` interface now returns `java.util.concurrent.CompletionStage` instead of `F.Promise`. +* Static methods that rely on `Play.current` or `Play.application` have been deprecated. +* Play WS would infer a charset from the content type and append a charset to the `Content-Type` header of the request if one was not already set. This caused some confusion and bugs, and so in 2.5.x the `Content-Type` header does not automatically include an inferred charset. If you explicitly set a `Content-Type` header, the setting is honored as is. + +## Deprecated `GlobalSettings` + +As part of the on going efforts to move away from global state in Play, `GlobalSettings` and the application `Global` object have been deprecated. For more details, see the [[Play 2.4 migration guide|GlobalSettings]] for how to migrate away from using `GlobalSettings`. + +## Removed Plugins API + +The Plugins API was deprecated in Play 2.4 and has been removed in Play 2.5. The Plugins API has been superseded by Play's dependency injection and module system which provides a cleaner and more flexible way to build reusable components. For details on how to migrate from plugins to dependency injection see the [[Play 2.4 migration guide|PluginsToModules]]. + +## Routes generated with InjectedRoutesGenerator + +Routes are now generated using the dependency injection aware `InjectedRoutesGenerator`, rather than the previous `StaticRoutesGenerator` which assumed controllers were singleton objects. + +To revert back to the earlier behavior (if you have "object MyController" in your code, for example), please add the following line to your `build.sbt` file: + +```scala +routesGenerator := StaticRoutesGenerator +``` + +If you're using `Build.scala` instead of `build.sbt` you will need to import the `routesGenerator` settings key: + +````scala +import play.sbt.routes.RoutesCompiler.autoImport._ +```` + +Using static controllers with the static routes generator is not deprecated, but it is recommended that you migrate to using classes with dependency injection. + +## Replaced static controllers with dependency injection + +`controllers.ExternalAssets` is now a class, and has no static equivalent. `controllers.Assets` and `controllers.Default` are also classes, and while static equivalents exist, it is recommended that you use the class version. + +### How to migrate + +The recommended solution is to use classes for all your controllers. The `InjectedRoutesGenerator` is now the default, so the controllers in the routes file are assumed to be classes instead of objects. + +If you still have static controllers, you can use `StaticRoutesGenerator` (described above) and add the `@` symbol in front of the route in the `routes` file, e.g. + +``` +GET /assets/*file @controllers.ExternalAssets.at(path = "/public", file) +``` + +## Deprecated play.Play and play.api.Play methods + +The following methods have been deprecated in `play.Play`: + +* `public static Application application()` +* `public static Mode mode()` +* `public static boolean isDev()` +* `public static boolean isProd()` +* `public static boolean isTest()` + +Likewise, methods in `play.api.Play` that take an implicit `Application` and delegate to Application, such as `def classloader(implicit app: Application)` are now deprecated. + +### How to migrate + +These methods delegate to either `play.Application` or `play.Environment` -- code that uses them should use dependency injection to inject the relevant class. + +You should refer to the list of dependency injected components in the [[Play 2.4 Migration Guide|Migration24#Dependency-Injected-Components]] to migrate built-in Play components. + +For example, the following code injects an environment and configuration into a Controller in Scala: + +```scala +class HomeController @Inject() (environment: play.api.Environment, + configuration: play.api.Configuration) + extends Controller { + + def index = Action { + Ok(views.html.index("Your new application is ready.")) + } + + def config = Action { + Ok(configuration.underlying.getString("some.config")) + } + + def count = Action { + val num = environment.resource("application.conf").toSeq.size + Ok(num.toString) + } +} +``` + +### Handling legacy components + +Generally the components you use should not need to depend on the entire application, but sometimes you have to deal with legacy components that require one. You can handle this by injecting the application into one of your components: + +```scala +class FooController @Inject() (appProvider: Provider[Application]) + extends Controller { + implicit lazy val app = appProvider.get() + def bar = Action { + Ok(Foo.bar(app)) + } +} +``` + +Note that you usually want to use a `Provider[Application]` in this case to avoid circular dependencies. + +Even better, you can make your own `*Api` class that turns the static methods into instance methods: + +```scala +class FooApi @Inject() (appProvider: Provider[Application]) { + implicit lazy val app = appProvider.get() + def bar = Foo.bar(app) + def baz = Foo.baz(app) +} +``` + +This allows you to benefit from the testability you get with DI and still use your library that uses global state. + +## Content-Type charset changes + +Prior to Play 2.5, Play would add a `charset` parameter to certain content types that do not define a charset parameter, specifically [`application/json`](https://www.iana.org/assignments/media-types/application/json) and [`application/x-www-form-urlencoded`](https://www.iana.org/assignments/media-types/application/x-www-form-urlencoded). Now the `Content-Type` is sent without a charset by default. This applies both to sending requests with `WS` and returning responses from Play actions. If you have a non-spec-compliant client or server that requires you to send a charset parameter, you can explicitly set the `Content-Type` header. + +## Guice injector and Guice builder changes + +By default, Guice can resolve your circular dependency by proxying an interface in the cycle. Since circular dependencies are generally a code smell, and you can also inject Providers to break the cycle, we have chosen to disable this feature on the default Guice injector. Other DI frameworks also are not likely to have this feature, so it can lead to problems when writing Play modules. + +Now there are four new methods on the Guice builders (`GuiceInjectorBuilder` and `GuiceApplicationBuilder`) for customizing how Guice injects your classes: +* `disableCircularProxies`: disables the above-mentioned behaviour of proxying interfaces to resolve circular dependencies. To allow proxying use `disableCircularProxies(false)`. +* `requireExplicitBindings`: instructs the injector to only inject classes that are explicitly bound in a module. Can be useful in testing for verifying bindings. +* `requireAtInjectOnConstructors`: requires a constructor annotated with @Inject to instantiate a class. +* `requireExactBindingAnnotations`: disables the error-prone feature in Guice where it can substitute a binding for @Named Foo when injecting @Named("foo") Foo. + +## CSRF changes + +In order to make Play's CSRF filter more resilient to browser plugin vulnerabilities and new extensions, the default configuration for the CSRF filter has been made far more conservative. The changes include: + +* Instead of blacklisting `POST` requests, now only `GET`, `HEAD` and `OPTIONS` requests are whitelisted, and all other requests require a CSRF check. This means `DELETE` and `PUT` requests are now checked. +* Instead of blacklisting `application/x-www-form-urlencoded`, `multipart/form-data` and `text/plain` requests, requests of all content types, including no content type, require a CSRF check. One consequence of this is that AJAX requests that use `application/json` now need to include a valid CSRF token in the `Csrf-Token` header. +* Stateless header-based bypasses, such as the `X-Requested-With`, are disabled by default. + +There's a new config option to bypass the new CSRF protection for requests with certain headers. This config option is turned on by default for the Cookie and Authorization headers, so that REST clients, which typically don't use session authentication, will still work without having to send a CSRF token. + +However, since the config option allows through *all* requests without those headers, applications that use other authentication schemes (NTLM, TLS client certificates) will be vulnerable to CSRF. These applications should disable the config option so that their authenticated (cookieless) requests are protected by the CSRF filter. + +Finally, an additional option has been added to disable the CSRF check for origins trusted by the CORS filter. Please note that the CORS filter must come *before* the CSRF filter in your filter chain for this to work! + +Play's old default behaviour can be restored by adding the following configuration to `application.conf`: + +``` +play.filters.csrf { + header { + bypassHeaders { + X-Requested-With = "*" + Csrf-Token = "nocheck" + } + protectHeaders = null + } + bypassCorsTrustedOrigins = false + method { + whiteList = [] + blackList = ["POST"] + } + contentType.blackList = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"] +} +``` + +### Getting the CSRF token + +Previously, a CSRF token could be retrieved from the HTTP request in any action. Now you must have either a CSRF filter or a CSRF action for `CSRF.getToken` to work. If you're not using a filter, you can use the `CSRFAddToken` action in Scala or `AddCSRFToken` Java annotation to ensure a token is in the session. + +Also, a minor bug was fixed in this release in which the CSRF token would be empty (throwing an exception in the template helper) if its signature was invalid. Now it will be regenerated on the same request so a token is still available from the template helpers and `CSRF.getToken`. + +For more details, please read the CSRF documentation for [[Java|JavaCsrf]] and [[Scala|ScalaCsrf]]. + +## Crypto Deprecated + +From Play 1.x, Play has come with a `Crypto` object that provides some cryptographic operations. This used internally by Play. The `Crypto` object is not mentioned in the documentation, but is mentioned as “cryptographic utilities” in the scaladoc. + +For a variety of reasons, providing cryptographic utilities as a convenience has turned out not to be workable. In 2.5.x, the Play-specific functionality has been broken into `CookieSigner`, `CSRFTokenSigner` and `AESSigner` traits, and the `Crypto` singleton object deprecated. + +### How to Migrate + +Cryptographic migration will depend on your use case, especially if there is unsafe construction of the cryptographic primitives. The short version is to use [Kalium](https://abstractj.github.io/kalium/) if possible, otherwise use [KeyCzar](https://github.com/google/keyczar) or straight [JCA](https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html). + +Please see [[Crypto Migration|CryptoMigration25]] for more details. + +## Netty 4 upgrade + +Netty has been upgraded from 3.10 to 4.0. One consequence of this is the configuration options for configuring Netty channel options have changed. The full list options can be seen [here](https://netty.io/4.0/api/io/netty/channel/ChannelOption.html). + +### How to Migrate + +Modify any `play.server.netty.option` keys to use the new keys defined in [ChannelOption](https://netty.io/4.0/api/io/netty/channel/ChannelOption.html). A mapping of some of the more popularly used ones is: + +| **Old** | **New** | +| ------------------ +| `play.server.netty.option.backlog` | `play.server.netty.option.SO_BACKLOG` | +| `play.server.netty.option.child.keepAlive` | `play.server.netty.option.child.SO_KEEPALIVE` | +| `play.server.netty.option.child.tcpNoDelay` | `play.server.netty.option.child.TCP_NODELAY` | + +## Changes to `sendFile`, `sendPath` and `sendResource` methods + +Java (`play.mvc.StatusHeader`) and Scala (`play.api.mvc.Results.Status`) APIs had the following behavior before: + +| API | Method | Default | +|:------|:-------------------------------------------|:-------------| +| Scala | `play.api.mvc.Results.Status.sendResource` | `inline` | +| Scala | `play.api.mvc.Results.Status.sendPath` | `attachment` | +| Scala | `play.api.mvc.Results.Status.sendFile` | `attachment` | +| Java | `play.mvc.StatusHeader.sendInputStream` | `none` | +| Java | `play.mvc.StatusHeader.sendResource` | `inline` | +| Java | `play.mvc.StatusHeader.sendPath` | `attachment` | +| Java | `play.mvc.StatusHeader.sendFile` | `inline` | + +In other words, they were mixing `inline` and `attachment` modes when delivering files. Now, when delivering files, paths and resources uses `inline` as the default behavior. Of course, you can alternate between these two modes using the parameters present in these methods. diff --git a/manual/releases/release25/migration25/StreamsMigration25.md b/manual/releases/release25/migration25/StreamsMigration25.md new file mode 100644 index 00000000..d82534e6 --- /dev/null +++ b/manual/releases/release25/migration25/StreamsMigration25.md @@ -0,0 +1,438 @@ + +# Streams Migration Guide + +Play 2.5 has made several major changes to how it streams data and response bodies. + +1. Play 2.5 uses **Akka Streams for streaming**. Previous versions of Play used iteratees for streaming as well as several other ad-hoc types of streaming, such as `WebSocket`, `Chunks`, etc. + + There are two main benefits of the change to use Akka Streams. First, Java users can now access the full feature set of Play, e.g. writing body parsers and filters. Second, the streaming library is now more consistent across Play. + +2. Play 2.5 uses **`ByteString` to hold packets of bytes**. Previously, Play used byte arrays (`byte[]`/`ArrayByte`) to hold bytes. + + The `ByteString` class is immutable like Java's `String`, so it's safer and easier to use. Like `String` it does have a small performance cost, because it copies its data when it is constructed, but this is balanced by its cheap concatenation and substring operations. + +3. Play 2.5 has a new **`HttpEntity` type for response bodies**. Previously response bodies were a plain stream of bytes. HTTP bodies are now a type of `HttpEntity`: `Strict`, `Streamed` or `Chunked`. + + By telling Play what type of entity to use, applications can have more control over how Play sends HTTP responses. It also makes it easier for Play to optimize how it delivers the body. + +## Summary of changes + +The following parts of the Play API have been updated: + +- results (`Result` body, `chunked`/`feed`/`stream` methods) +- actions (`EssentialAction`) +- body parsing (`BodyParser`) +- WebSockets (`WebSocket`) +- Server-Sent Events (`EventSource`) + +The following types have been changed: + +| **Purpose** | **Old types** | **New type** +| ------- +| Holding bytes | `byte[]`/`Array[Byte]` | `ByteString` +| Producing a stream | `Enumerator`, `WebSocket.Out`, `Chunks.Out`, `EventSource.Out` | `Source` +| Transforming a stream to another stream | `Enumeratee` | `Flow` +| Transforming a stream to a single value | `Iteratee` | `Accumulator` +| Consuming a stream | `Iteratee` | `Sink` + + +## How to migrate (by API) + +The following section gives an overview of how to migrate code that uses different parts of the API. + +### Migrating chunked results (`chunked`, `Results.Chunked`) + +In Play 2.4 you would create chunked results in Scala with an `Enumerator` and in Java with a `Results.Chunked` object. In Play 2.5 these parts of the API are still available, but they have been deprecated. + +If you choose to migrate to the new API, you can create a chunked result by calling the `chunked` method on a `StatusHeader` object and providing an Akka Streams `Source` object for the stream of chunks. + +More advanced users may prefer to explicitly create an `HttpEntity.Chunked` object and pass it into the `Result` object constructor. + +* To learn how to migrate an Enumerator to a Source, see [Migrating Enumerators to Sources](#Migrating-Enumerators-to-Sources). + +### Migrating streamed results (`feed`, `stream`) (Scala only) + +In Play 2.4 Scala users could stream results by passing an `Enumerator` to the `feed` or `stream` method. (Java users didn't have a way to stream results, apart from chunked results.) The `feed` method streamed the `Enumerator`'s data then closed the connection. The `stream` method, either streamed or chunked the result and possibly closed the connection, depending on the HTTP version of the connection and the presence or absence of the `Content-Length` header. + +In Play 2.5, the `stream` method has been removed and the `feed` method has been deprecated. You can choose whether or not to migrate the `feed` method to the new API. If you use the `stream` method your code will need to be changed. + +The new API is to create a `Result` object directly and choose an `HttpEntity` to represent its body. For streamed results, you can use the `HttpEntity.Streamed` class. The `Streamed` class takes a `Source` as a body and an optional `Content-Length` header value. The `Source`'s content will be sent to the client. If the entity has a `Content-Length` header then the connection will be left open, otherwise it will be closed to signal the end of the stream. + +* To learn how to migrate an Enumerator to a Source, see [Migrating Enumerators to Sources](#Migrating-Enumerators-to-Sources). + +### Migrating WebSockets (`WebSocket`) + +In Play 2.4, a WebSocket's bidirectional stream was represented in Java with a pair of `WebSocket.In` and `WebSocket.Out` objects and in Scala with a pair of `Enumerator` and `Iteratee` objects. In Play 2.5, both Java and Scala now use an Akka Streams `Flow` to represent the bidirectional stream. + +To migrate your WebSockets code in Play 2.5 you have two options. + +The first option is to use the old Play API, which has been deprecated and renamed to `LegacyWebSocket`. This is the easiest option. You just need to change your code that refers to `WebSocket` to refer to `LegacyWebSocket` instead. The `LegacyWebSocket` class gives you an easy migration path from Play 2.4 to Play 2.5. + +The second option is to change to the new Play API. To do this you'll need to change your WebSocket code to use an Akka Streams `Flow` object. + +#### Migrating Scala WebSockets + +The Play 2.4 Scala WebSocket API requires an `Enumerator`/`Iteratee` pair that produces `In` objects and consumes `Out` objects. A pair of `FrameFormatter`s handle the job of getting the data out of the `In` and `Out` objects. + +```scala +case class WebSocket[In, Out](f: RequestHeader => Future[Either[Result, (Enumerator[In], Iteratee[Out, Unit]) => Unit]])(implicit val inFormatter: WebSocket.FrameFormatter[In], val outFormatter: WebSocket.FrameFormatter[Out]) extends Handler { +``` + +```scala +trait FrameFormatter[A] { + def transform[B](fba: B => A, fab: A => B): FrameFormatter[B] +} +``` + +The Play 2.5 Scala WebSocket API is built around a `Flow` of `Message`s. A [`Message`](api/scala/play/api/http/websocket/Message.html) represents a [WebSocket frame](https://tools.ietf.org/html/rfc6455#section-5). The `MessageFlowTransformer` type handles transforming high-level objects, like JSON, XML and bytes into `Message` frames. A set of built-in implicit `MessageFlowTransformer`s are provided, and you can also write your own. + +```scala +trait WebSocket extends Handler { + def apply(request: RequestHeader): Future[Either[Result, Flow[Message, Message, _]]] +} +``` + +```scala +sealed trait Message +case class TextMessage(data: String) extends Message +case class BinaryMessage(data: ByteString) extends Message +case class CloseMessage(statusCode: Option[Int] = Some(CloseCodes.Regular), reason: String = "") extends Message +case class PingMessage(data: ByteString) extends Message +case class PongMessage(data: ByteString) extends Message +``` + +```scala +trait MessageFlowTransformer[+In, -Out] { self => + def transform(flow: Flow[In, Out, _]): Flow[Message, Message, _] +} +``` + +To migrate, you'll need to translate the bidirectional `Enumerator`/`Iteratee` stream into a `Flow`. You may also need to convert your `In`/`Out` objects into `Message`s using a `MessageFlowTransformer`, although this is not necessary for common types like JSON, since some built-in implicit conversions are provided. + +* To learn how to migrate an Enumerator to a Source, see [Migrating Enumerators to Sources](#Migrating-Enumerators-to-Sources). +* To learn how to migrate an Iteratee to a Sink, see [Migrating Iteratees to Sinks and Accumulators](#Migrating-Iteratees-to-Sinks-and-Accumulators). + +#### Migrating Java WebSockets + +The Play 2.4 Java WebSocket API uses a `WebSocket.In` object to handle incoming messages and a `WebSocket.Out` object to send outgoing messages. The API supported WebSockets transporting text, bytes or JSON frames. + +```java +return WebSocket.whenReady((in, out) -> { + out.write("Hello!"); + out.close(); +}); +``` + +The new Play 2.5 API is much more powerful. You can now create a `WebSocket` and return arbitrary WebSocket `Message` frames. The bidirectional `Message` streams are represented as a `Flow`. + +```java +public abstract class WebSocket { + public abstract CompletionStage>> apply(Http.RequestHeader request); +} +``` + +If you want to convert WebSocket `Message` frames to your own types you can use the `MappedWebSocketAcceptor` class. Several of these classes are provided for you: `Text`, `Binary` and `Json`. For example: + +```java +return WebSocket.Text.accept(requestHeader -> { + // return a Flow +}) +``` + +You can also create your own `MappedWebSocketAcceptor` by defining how to convert incoming outgoing messages. + +### Migrating Comet + +To use [Comet](https://en.wikipedia.org/wiki/Comet_(programming)) in Play you need to produce a chunked HTTP response with specially formatted chunks. Play has a `Comet` class to help produce events on the server that can be sent to the browser. In Play 2.4.x, a new Comet instance had to be created and used callbacks for Java, and an Enumeratee was used for Scala. In Play 2.5, there are new APIs added based on Akka Streams. + +#### Migrating Java Comet + +Create an Akka Streams source for your objects, and convert them into either `String` or `JsonNode` objects. From there, you can use `play.libs.Comet.string` or `play.libs.Comet.json` to convert your objects into a format suitable for `Results.ok().chunked()`. There is additional documentation in [[JavaComet]]. + +Because the Java Comet helper is based around callbacks, it may be easier to turn the callback based class into a `org.reactivestreams.Publisher` directly and use `Source.fromPublisher` to create a source. + +#### Migrating Scala Comet + +Create an Akka Streams source for your objects, and convert them into either `String` or `JsValue` objects. From there, you can use `play.api.libs.Comet.string` or `play.api.libs.Comet.json` to convert your objects into a format suitable for `Ok.chunked()`. There is additional documentation in [[ScalaComet]]. + +### Migrating Server-Sent events (`EventSource`) + +To use [Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events) in Play you need to produce a chunked HTTP response with specially formatted chunks. Play has an `EventSource` interface to help produce events on the server that can be sent to the browser. In Play 2.4 Java and Scala each had quite different APIs, but in Play 2.5 they have been changed so they're both based on Akka Streams. + +#### Migrating Java Server-Sent events + +In Play 2.4's Java API you produce your stream of chunks with `EventSource`, which is a class that extends `Chunks`. You can construct `Event` objects from strings or JSON objects and then send them in the response by calling `EventSource`'s `send` method. + +```java +EventSource eventSource = new EventSource() { + @Override + public void onConnected() { + send(Event.event("hello")); + send(Event.event("world")); + ... + } +}; +return ok(eventSource); +``` + +In Play 2.5 you'll typically create an Akka Streams `Source` for your application objects, use `Source.map` to convert your objects to `Event`s then finally use `EventSource.chunked` to convert the `Event`s into chunked values. The example below shows how this works for sending a stream of strings. + +```java +Source stringSource = ...; +Source eventSource = myStrings.map(Event::event); +return ok().chunked(EventSource.chunked(eventSource)).as("text/event-stream"); +``` + +* To migrate `EventSource.onConnected`, `EventSource.send`, etc to a `Source`, implement `org.reactivestreams.Publisher` on the class and use `Source.fromPublisher` to create a source from the callbacks. + +If you still want to use the same API as in Play 2.4 you can use the `LegacyEventSource` class. This class is the same as the Play 2.4 API, but it has been renamed and deprecated. If you want to use the new API, but retain the same feel as the old imperative API, you can try [`GraphStage`](https://doc.akka.io/docs/akka/2.4.3/java/stream/stream-customize.html#custom-processing-with-graphstage). + +#### Migrating Scala Server-Sent events + +To use Play 2.4's Scala API you provide an `Enumerator` of application objects then use the `EventSource` `Enumeratee` to convert them into `Event`s. Finally you pass the `Event`s to the `chunked` method where they're converted into chunks. + +```scala +val someDataStream: Enumerator[SomeData] = ??? +Ok.chunked(someDataStream &> EventSource()) +``` + +In Play 2.5 using `EventSource` with `Enumerator`s and `Enumeratee`s has been deprecated. You can still use an `Enumerator` and `Enumeratee`, but it is recommended that you convert your code to use a `Source` and a `Flow` instead. The `Source` produces the stream of objects and `EventSource.flow`'s `Flow` converts them into `Event`s. For example, code above would be rewritten as: + +```scala +val someDataStream: Source[SomeData, Unit] = ??? +Ok.chunked(someDataStream via EventSource.flow).as("text/event-stream") +``` + +* To learn how to migrate an Enumerator to a Source, see [Migrating Enumerators to Sources](#Migrating-Enumerators-to-Sources). + +### Migrating custom actions (`EssentialAction`) (Scala only) + +Most Scala users will use the `Action` class for their actions. The `Action` class is a type of `EssentialAction` that always parses its body fully before running its logic and sending a result. Some users may have written their own custom `EssentialAction`s so that they can do things like incrementally processing the request body. + +If you're only using normal `Action`s in your Play 2.4 application then they do not need any migration. However, if you've written an `EssentialAction`, then you'll need to migrate it to the new API in Play 2.5. The behavior of an `EssentialAction` is still the same, but the signature has changed from Play 2.4: + +```scala +trait EssentialAction extends (RequestHeader => Iteratee[Array[Byte], Result]) +``` + +to a new signature in Play 2.5: + +```scala +trait EssentialAction extends (RequestHeader => Accumulator[ByteString, Result]) +``` + +To migrate, you'll need to replace your `Iteratee` with an `Accumulator` and your `Array[Byte]` with a `ByteString`. + +* To learn how to migrate an Iteratee to an Accumulator, see [Migrating Iteratees to Sinks and Accumulators](#Migrating-Iteratees-to-Sinks-and-Accumulators). +* To learn how to migrate an `Array[Byte]` to a `ByteString` see [Migrating byte arrays to ByteStrings](#Migrating-byte-arrays-\(byte[]/Array[Byte]\)-to-ByteStrings). + +### Migrating custom body parsers (`BodyParser`) (Scala only) + +If you're a Scala user who has a custom `BodyParser` in their Play 2.4 application then you'll need to migrate it to the new Play 2.5 API. The `BodyParser` trait signature looks like this in Play 2.4: + +```scala +trait BodyParser[+A] extends (RequestHeader => Iteratee[Array[Byte], Either[Result, A]]) +``` + +In Play 2.5 it has changed to use Akka Streams types: + +```scala +trait BodyParser[+A] extends (RequestHeader => Accumulator[ByteString, Either[Result, A]]) +``` + +To migrate, you'll need to replace your `Iteratee` with an `Accumulator` and your `Array[Byte]` with a `ByteString`. + +* To learn how to migrate an Iteratee to an Accumulator, see [Migrating Iteratees to Sinks and Accumulators](#Migrating-Iteratees-to-Sinks-and-Accumulators). +* To learn how to migrate an `Array[Byte]` to a `ByteString` see [Migrating byte arrays to ByteStrings](#Migrating-byte-arrays-\(byte[]/Array[Byte]\)-to-ByteStrings). + +### Migrating `Result` bodies (Scala only) + +The `Result` object has changed how it represents the result body and the connection close flag. Instead of taking `body: Enumerator[Array[Byte]], connection: Connection`, it now takes `body: HttpEntity`. The `HttpEntity` type contains information about the body and implicit information about how to close the connection. + +You can migrate your existing `Enumerator` by using a `Streamed` entity that contains a `Source` and an optional `Content-Length` and `Content-Type` header. + +```scala +val bodyPublisher: Publisher[ByteString] = Streams.enumeratorToPublisher(bodyEnumerator) +val bodySource: Source[ByteString, _] = Source.fromPublisher(bodyPublisher) +val entity: HttpEntity = HttpEntity.Streamed(bodySource) +new Result(headers, entity) +``` + +See the section on migrating `Enumerator`s and migrating to `ByteString` for more information on migrating to these types. + +* To learn how to migrate an Iteratee to an Accumulator, see [Migrating Iteratees to Sinks and Accumulators](#Migrating-Iteratees-to-Sinks-and-Accumulators). +* To learn how to migrate an `Array[Byte]` to a `ByteString` see [Migrating byte arrays to ByteStrings](#Migrating-byte-arrays-\(byte[]/Array[Byte]\)-to-ByteStrings). + +You may find that you don't need a stream for the `Result` body at all. If that's the case you might want to use a `Strict` entity for the body. + +```scala +new Result(headers, HttpEntity.Strict(bytes)) +``` + +## How to migrate (by type) + +This section explains how to migrate your byte arrays and streams to the new Akka Streams APIs. + +Akka Streams is part of the Akka project. Play uses Akka Streams to provide streaming functionality: sending and receiving sequences of bytes and other objects. The Akka project has a lot of good documentation about Akka Streams. Before you start using Akka Streams in Play it is worth looking at the Akka Streams documentation to see what information is available. + +* [Documentation for Java](https://doc.akka.io/docs/akka/2.4.3/java/stream/index.html) +* [Documentation for Scala](https://doc.akka.io/docs/akka/2.4.3/scala/stream/index.html) + +The API documentation can be found under the `akka.stream` package in the main Akka API documentation: + +* [Akka Javadoc](https://doc.akka.io/japi/akka/2.4.3/) +* [Akka Scala](https://doc.akka.io/api/akka/2.4.3/) + +When you're first getting started with Akka Streams, the *Basics and working with Flows* section of the Akka documentation is worth a look. It will introduce you to the most important parts of the Akka Streams API. + +* [Basics for Java](https://doc.akka.io/docs/akka/2.4.3/java/stream/stream-flows-and-basics.html) +* [Basics for Scala](https://doc.akka.io/docs/akka/2.4.3/scala/stream/stream-flows-and-basics.html) + +You don't need to convert your whole application in one go. Parts of your application can keep using iteratees while other parts use Akka Streams. Akka Streams provides a [reactive streams](http://reactive-streams.org) implementation, and Play's iteratees library also provides a reactive streams implementation, consequently, Play's iteratees can easily be wrapped in Akka Streams and vice versa. + +### Migrating byte arrays (`byte[]`/`Array[Byte]`) to `ByteString`s + +Refer to the [Java](https://doc.akka.io/japi/akka/2.4.3/index.html) and [Scala](https://doc.akka.io/api/akka/2.4.3/akka/util/ByteString.html) API documentation for `ByteString`. + +Examples: + +Scala: + +```scala +// Get the empty ByteString (this instance is cached) +ByteString.empty +// Create a ByteString from a String +ByteString("hello") +ByteString.fromString("hello") +// Create a ByteString from an Array[Byte] +ByteString(arr) +ByteString.fromArray(arr) +``` + +Java: + +```java +// Get the empty ByteString (this instance is cached) +ByteString.empty(); +// Create a ByteString from a String +ByteString.fromString("hello"); +// Create a ByteString from an Array[Byte] +ByteString.fromArray(arr); +``` + +### Migrating `*.Out`s to `Source`s + +Play now uses a `Source` to generate events instead of its old `WebSocket.Out`, `Chunks.Out` and `EventSource.Out` classes. These classes were simple to use, but they were inflexible and they didn't implement [back](https://doc.akka.io/docs/akka/2.4.3/java/stream/stream-flows-and-basics.html#back-pressure-explained) [pressure](https://doc.akka.io/docs/akka/2.4.3/scala/stream/stream-flows-and-basics.html#back-pressure-explained) properly. + +You can replace your `*.Out` class with any `Source` that produces a stream. There are lots of ways to create `Source`s ([Java](https://doc.akka.io/docs/akka/2.4.3/java/stream/stream-flows-and-basics.html#Defining_sources__sinks_and_flows)/[Scala](https://doc.akka.io/docs/akka/2.4.3/scala/stream/stream-flows-and-basics.html#Defining_sources__sinks_and_flows). + +If you want to replace your `*.Out` with a simple object that you can write messages to and then close, without worrying about back pressure, then you can use the `Source.actorRef` method: + +Java: + +```java +Source source = Source.actorRef(256, OverflowStrategy.dropNew()) + .mapMaterializedValue(sourceActor -> { + sourceActor.tell(ByteString.fromString("hello"), null); + sourceActor.tell(ByteString.fromString("world"), null); + sourceActor.tell(new Status.Success(NotUsed.getInstance()), null); + return null; + }); +``` + +Scala: + +```scala +val source = Source.actorRef[ByteString](256, OverflowStrategy.dropNew).mapMaterializedValue { sourceActor => + sourceActor ! ByteString("hello") + sourceActor ! ByteString("world") + sourceActor ! Status.Success(()) // close the source +} +``` + +### Migrating `Enumerator`s to `Source`s + +Play uses `Enumerator`s in many places to produce streams of values. + +**Step 1:** Use transitional API (if available) + +If you use `Results.chunked` or `Results.feed` you can continue to use the existing methods. These methods have been deprecated, so you may want to change your code anyway. + +**Step 2:** Convert `Enumerator` to `Source` with an adapter + +You can convert your existing `Enumerator` to a `Source` by first converting it to a reactive streams `Publisher` using `Streams.enumeratorToPublisher`, and then you can convert the publisher to a source using [`Source.fromPublisher`](https://doc.akka.io/api/akka/2.4.3/akka/stream/scaladsl/Source$.html#fromPublisher[T]\(Publisher[T]\):Source[T,NotUsed]), for example: + +```scala +val enumerator: Enumerator[T] = ... +val source = Source.fromPublisher(Streams.enumeratorToPublisher(enumerator)) +``` + +**Step 3:** (Optional) Rewrite to a `Source` + +Here's a list of some common mappings for enumerator factory methods: + +| **Iteratees** | **Akka Streams** | **Notes** | +| -------- +| `Enumerator.apply(a)` | `Source.single(a)` | | +| `Enumerator.apply(a, b)` | `Source.apply(List(a, b)))` | | +| `Enumerator.enumerate(seq)` | `Source.apply(seq)` | `seq` must be immutable | +| `Enumerator.repeat` | `Source.repeat` | The repeated element is not evaluated each time in Akka Streams | +| `Enumerator.empty` | `Source.empty` | | +| `Enumerator.unfold` | `Source.unfold` | | +| `Enumerator.generateM` | `Source.unfoldAsync` | | +| `Enumerator.fromStream` | `StreamConverters.fromInputStream` | | +| `Enumerator.fromFile` | `StreamConverters.fromInputStream` | You have to create an `InputStream` for the `java.io.File` | + +### Migrating `Iteratee`s to `Sink`s and `Accumulator`s + +**Step 1:** Convert using an adapter + +You can convert your existing `Iteratee` to a `Sink` by first converting it to a reactive streams `Subscriber` using `Streams.iterateeToSubscriber`, and then you can convert the subscriber to a sink using [`Sink.fromSubscriber`](https://doc.akka.io/api/akka/2.4.3/akka/stream/scaladsl/Sink$.html#fromSubscriber[T]\(Subscriber[T]\):Sink[T,NotUsed]), for example: + +```scala +val iteratee: Iteratee[T, U] = ... +val (subscriber, resultIteratee) = Streams.iterateeToSubscriber(iteratee) +val sink = Sink.fromSubscriber(subscriber) +``` + +If you need to return an `Accumulator`, you can instead use the `Streams.iterateeToAccumulator` method. + +**Step 2:** (Optional) Rewrite to a `Sink` + +Here's a list of some common mappings for iteratee factory methods: + +| **Iteratees** | **Akka Streams** | **Notes** | +| -------- +| `Iteratee.fold` | `Sink.fold` | | +| `Iteratee.head` | `Sink.headOption` | | +| `Iteratee.getChunks` | `Sink.seq` | | +| `Iteratee.foreach` | `Sink.foreach` | | +| `Iteratee.ignore` | `Sink.ignore` | | +| `Done` | `Sink.cancelled` | The materialized value can be mapped to produce the result, or if using accumulators, `Accumulator.done` can be used instead. | + +### Migrating `Enumeratees`s to `Processor`s + +**Step 1:** Convert using an adapter + +You can convert your existing `Enumeratee` to a `Flow` by first converting it to a reactive streams `Processor` using `Streams.enumerateeToProcessor`, and then you can convert the processor to a flow using [`Flow.fromProcessor`](https://doc.akka.io/api/akka/2.4.3/akka/stream/scaladsl/Flow$.html#fromProcessor[I,O]\(\(\)⇒Processor[I,O]\):Flow[I,O,NotUsed]), for example: + +```scala +val enumeratee: Enumeratee[A, B] = ... +val flow = Flow.fromProcessor(() => Streams.enumerateeToProcessor(enumeratee)) +``` + +**Step 2:** (Optional) Rewrite to a `Flow` + +Here's a list of some common mappings for enumeratee factory methods: + +| **Iteratees** | **Akka Streams** | **Notes** | +| -------- +| `Enumeratee.map` | `Flow.map` | | +| `Enumeratee.mapM` | `Flow.mapAsync` | You have to specify the parallelism in Akka Streams, ie how many elements will be mapped in parallel at a time. | +| `Enumeratee.mapConcat` | `Flow.mapConcat` | | +| `Enumeratee.filter` | `Flow.filter` | | +| `Enumeratee.take` | `Flow.take` | | +| `Enumeratee.takeWhile` | `Flow.takeWhile` | | +| `Enumeratee.drop` | `Flow.drop` | | +| `Enumeratee.dropWhile` | `Flow.dropWhile` | | +| `Enumeratee.collect` | `Flow.collect` | | diff --git a/manual/releases/release25/migration25/index.toc b/manual/releases/release25/migration25/index.toc new file mode 100644 index 00000000..6e0ca978 --- /dev/null +++ b/manual/releases/release25/migration25/index.toc @@ -0,0 +1,4 @@ +Migration25:Migration Guide +StreamsMigration25:Streams Migration Guide +JavaMigration25:Java Migration Guide +CryptoMigration25:Crypto Migration Guide diff --git a/manual/releases/release26/Highlights26.md b/manual/releases/release26/Highlights26.md new file mode 100644 index 00000000..16319437 --- /dev/null +++ b/manual/releases/release26/Highlights26.md @@ -0,0 +1,746 @@ + +# What's new in Play 2.6 + +This page highlights the new features of Play 2.6. If you want to learn about the changes you need to make when you migrate to Play 2.6, check out the [[Play 2.6 Migration Guide|Migration26]]. + +## Scala 2.12 support + +Play 2.6 is the first release of Play to have been cross built against Scala 2.12 and 2.11. A number of dependencies were updated so that we can have support for both versions. + +You can select which version of Scala you would like to use by setting the `scalaVersion` setting in your `build.sbt`. + +For Scala 2.12: + +```scala +scalaVersion := "2.12.6" +``` + +For Scala 2.11: + +```scala +scalaVersion := "2.11.12" +``` + +## PlayService sbt plugin (experimental) + +As of Play 2.6.8, Play also offers a `PlayService` plugin. This is a much more minimal Play configuration oriented towards microservices. It uses the standard Maven layout instead of the traditional Play layout, and does not include twirl templates or the sbt-web functionality. For example: + +```scala +lazy val root = (project in file(".")) + .enablePlugins(PlayService) + .enablePlugins(RoutesCompiler) // place routes in src/main/resources, or remove if using SIRD/RoutingDsl + .settings( + scalaVersion := "2.12.6", + libraryDependencies ++= Seq( + guice, // remove if not using Play's Guice loader + akkaHttpServer, // or use nettyServer for Netty + logback // add Play logging support + ) + ) +``` + +**Note**: this plugin is considered *experimental*, which means the API may change. We expect it to be stable in Play 2.7.0. + +## "Global-State-Free" Applications + +The biggest under the hood change is that Play no longer relies on global state. You can still access the global application through `play.api.Play.current` / `play.Play.application()` in Play 2.6, but it is deprecated. This sets the stage for Play 3.0, where there is no global state at all. + +You can disable access to global application entirely by setting the following configuration value: + +``` +play.allowGlobalApplication=false +``` + +The above setting will cause an exception on any invocation of `Play.current`. + +## Akka HTTP Server Backend + +Play now uses the [Akka-HTTP](https://doc.akka.io/docs/akka-http/current/?language=scala) server engine as the default backend. More detail about Play's integration with Akka-HTTP can be found [[on the Akka HTTP Server page|AkkaHttpServer]]. There is an additional page on [[configuring Akka HTTP|SettingsAkkaHttp]]. + +The Netty backend is still available, and has been upgraded to use Netty 4.1. You can explicitly configure your project to use Netty [[on the NettyServer page|NettyServer]]. + +## HTTP/2 support (experimental) + +Play now has HTTP/2 support on the Akka HTTP server using the `PlayAkkaHttp2Support` module: + +``` +lazy val root = (project in file(".")) + .enablePlugins(PlayJava, PlayAkkaHttp2Support) +``` + +This automates most of the process of setting up HTTP/2. However, it does not work with the `run` command by default. See the [[Akka HTTP Server page|AkkaHttpServer]] for more details. + +## Request attributes + +Requests in Play 2.6 now contain *attributes*. Attributes allow you to store extra information inside request objects. For example, you can write a filter that sets an attribute in the request and then access the attribute value later from within your actions. + +Attributes are stored in a `TypedMap` that is attached to each request. `TypedMap`s are immutable maps that store type-safe keys and values. Attributes are indexed by a key and the key's type indicates the type of the attribute. + +Java: +```java +// Create a TypedKey to store a User object +class Attrs { + public static final TypedKey USER = TypedKey.create("user"); +} + +// Get the User object from the request +User user = req.attrs().get(Attrs.USER); +// Put a User object into the request +Request newReq = req.addAttr(Attrs.USER, newUser); +``` + +Scala: +```scala +// Create a TypedKey to store a User object +object Attrs { + val User: TypedKey[User] = TypedKey.apply[User]("user") +} + +// Get the User object from the request +val user: User = req.attrs(Attrs.User) +// Put a User object into the request +val newReq = req.addAttr(Attrs.User, newUser) +``` + +Attributes are stored in a `TypedMap`. You can read more about attributes in the `TypedMap` documentation: [Javadoc](api/java/play/libs/typedmap/TypedMap.html), [Scaladoc](api/scala/play/api/libs/typedmap/TypedMap.html). + +Request tags have now been deprecated and you should migrate to use attributes instead. See the [[tags section|Migration26#Request-tags-deprecation]] in the migration docs for more information. + +## Route modifier tags + +The routes file syntax now allows you to add "modifiers" to each route that provide custom behavior. We have implemented one such tag in the CSRF filter, the "nocsrf" tag. By default, the following route will not have the CSRF filter applied. + +``` ++ nocsrf # Don't CSRF protect this route +POST /api/foo/bar ApiController.foobar +``` + +You can also create your own modifiers: the `+` symbol can be followed by any number of whitespace-separated tags. + +These are made available in the `HandlerDef` request attribute (which also contains other metadata on the handler definition in the routes file): + +Java: +```java +import java.util.List; +import play.routing.HandlerDef; +import play.routing.Router; + +HandlerDef handler = req.attrs().get(Router.Attrs.HANDLER_DEF); +List modifiers = handler.getModifiers(); +``` + +Scala: +```scala +import play.api.routing.{ HandlerDef, Router } +import play.api.mvc.RequestHeader + +val handler = request.attrs(Router.Attrs.HandlerDef) +val modifiers = handler.modifiers +``` + +## Injectable Twirl Templates + +Twirl templates can now be created with a constructor annotation using `@this`. The constructor annotation means that Twirl templates can be injected into templates directly and can manage their own dependencies, rather than the controller having to manage dependencies not only for itself, but also for the templates it has to render. + +As an example, suppose a template has a dependency on a component `TemplateRenderingComponent`, which is not used by the controller. + +First create a file `IndexTemplate.scala.html` using the `@this` syntax for the constructor. Note that the constructor must be placed **before** the `@()` syntax used for the template's parameters for the `apply` method: + +```scala +@this(trc: TemplateRenderingComponent) +@(item: Item) + +@{trc.render(item)} +``` + +By default all generated Scala template classes Twirl creates with the `@this` syntax within Play will automatically be annotated with `@javax.inject.Inject()`. If desired you can change this behavior in `build.sbt`: + +```scala +// Add one or more annotation(s): +TwirlKeys.constructorAnnotations += "@java.lang.Deprecated()" + +// Or completely replace the default one with your own annotation(s): +TwirlKeys.constructorAnnotations := Seq("@com.google.inject.Inject()") +``` + +Now define the controller by injecting the template in the constructor: + +Java: +```java +public class MyController extends Controller { + + private final views.html.indexTemplate template; + + @Inject + public MyController(views.html.indexTemplate template) { + this.template = template; + } + + public Result index() { + return ok(template.render()); + } + +} +``` + +Scala: +```scala +class MyController @Inject()(indexTemplate: views.html.IndexTemplate, + cc: ControllerComponents) + extends AbstractController(cc) { + + def index = Action { implicit request => + Ok(indexTemplate()) + } +} +``` + +Once the template is defined with its dependencies, then the controller can have the template injected into the controller, but the controller does not see `TemplateRenderingComponent`. + +## Filters Enhancements + +Play now comes with a default set of enabled filters, defined through configuration. This provides a "secure by default" experience for new Play applications, and tightens security on existing Play applications. + +The following filters are enabled by default: + +* `play.filters.csrf.CSRFFilter` prevents CSRF attacks, see [[ScalaCsrf]] / [[JavaCsrf]] +* `play.filters.headers.SecurityHeadersFilter` prevents XSS and frame origin attacks, see [[SecurityHeaders]] +* `play.filters.hosts.AllowedHostsFilter` prevents DNS rebinding attacks, see [[AllowedHostsFilter]] + +In addition, filters can now be configured through `application.conf`. To append to the defaults list, use the `+=`: + +``` +play.filters.enabled+=MyFilter +``` + +If you want to specifically disable a filter for testing, you can also do that from configuration: + +``` +play.filters.disabled+=MyFilter +``` + +Please see [[the Filters page|Filters]] for more details. + +> **NOTE**: If you are migrating from an existing project that does not use CSRF form helpers such as `CSRF.formField`, then you may see "403 Forbidden" on PUT and POST requests, from the CSRF filter. To check this behavior, please add `` to your `logback.xml`. Likewise, if you are running a Play application on something other than localhost, you must configure the [[AllowedHostsFilter]] to specifically allow the hostname/ip you are connecting from. + +### gzip filter + +If you have the gzip filter enabled you can now also control which responses are and aren't gzipped based on their content types via `application.conf` (instead of writing you own `Filters` class): + +``` +play.filters.gzip { + + contentType { + + # If non empty, then a response will only be compressed if its content type is in this list. + whiteList = [ "text/*", "application/javascript", "application/json" ] + + # The black list is only used if the white list is empty. + # Compress all responses except the ones whose content type is in this list. + blackList = [] + } +} +``` + +Please see [[the gzip filter page|GzipEncoding]] for more details. + +## JWT Cookies + +Play now uses [JSON Web Token](https://tools.ietf.org/html/rfc7519) (JWT) format for session and flash cookies. This allows for a standardized signed cookie data format, cookie expiration (making replay attacks harder) and more flexibility in signing cookies. + +Please see [[Scala|ScalaSessionFlash]] or [[Java|JavaSessionFlash]] pages for more details. + +## Logging Marker API + + SLF4J Marker support has been added to [`play.Logger`](api/java/play/Logger.html) and [`play.api.Logger`](api/scala/play/api/Logger.html). + +In the Java API, it is a straight port of the SLF4J Logger API. This is useful, but you may find an SLF4J wrapper like [Godaddy Logger](https://github.com/godaddy/godaddy-logger) for a richer logging experience. + +In the Scala API, markers are added through a MarkerContext trait, which is added as an implicit parameter to the logger methods, i.e. + +```scala +import play.api._ +logger.info("some info message")(MarkerContext(someMarker)) +``` + +This opens the door for implicit markers to be passed for logging in several statements, which makes adding context to logging much easier without resorting to MDC. For example, using [Logstash Logback Encoder](https://github.com/logstash/logstash-logback-encoder#loggingevent_custom_event) and an [implicit conversion chain](https://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.html), request information can be encoded into logging statements automatically: + +@[logging-request-context-trait](../../working/scalaGuide/main/logging/code/ScalaLoggingSpec.scala) + +And then used in a controller and carried through `Future` that may use different execution contexts: + +@[logging-log-info-with-request-context](../../working/scalaGuide/main/logging/code/ScalaLoggingSpec.scala) + +Note that marker contexts are also very useful for "tracer bullet" style logging, where you want to log on a specific request without explicitly changing log levels. For example, you can add a marker only when certain conditions are met: + +@[logging-log-trace-with-tracer-controller](../../working/scalaGuide/main/logging/code/ScalaLoggingSpec.scala) + +And then trigger logging with the following TurboFilter in `logback.xml`: + +```xml + + TRACER_FILTER + TRACER + ACCEPT + +``` + +For more information, please see [[ScalaLogging|ScalaLogging#Using-Markers-and-Marker-Contexts]] or [[JavaLogging|JavaLogging#Using-Markers]]. + +For more information about using Markers in logging, see [TurboFilters](https://logback.qos.ch/manual/filters.html#TurboFilter) and [marker based triggering](https://logback.qos.ch/manual/appenders.html#OnMarkerEvaluator) sections in the Logback manual. + +## Configuration improvements + +In the Java API, we have moved to the standard `Config` object from Lightbend's Config library instead of `play.Configuration`. This brings the behavior in line with standard config behavior, as the methods now expect all keys to exist. See [[the Java config migration guide|JavaConfigMigration26]] for migration details. + +In the Scala API, we have introduced new methods to the `play.api.Configuration` class to simplify the API and allow loading of custom types. You can now use an implicit `ConfigLoader` to load any custom type you want. Like the `Config` API, the new `Configuration#get[T]` expects the key to exist by default and returns a value of type `T`, but there is also a `ConfigLoader[Option[T]]` that allows `null` config values. See the [[Scala configuration docs|ScalaConfig]] for more details. + +## Security Logging + +A security marker has been added for security related operations in Play, and failed security checks now log at WARN level, with the security marker set. This ensures that developers always know why a particular request is failing, which is important now that security filters are enabled by default in Play. + +The security marker also allows security failures to be triggered or filtered distinct from normal logging. For example, to disable all logging with the SECURITY marker set, add the following lines to the `logback.xml` file: + +```xml + + SECURITY + DENY + +``` + +In addition, log events using the security marker can also trigger a message to a Security Information & Event Management (SIEM) engine for further processing. + +## Configuring a Custom Logging Framework in Java + +Before, if you want to [[use a custom logging framework|SettingsLogger#Using-a-Custom-Logging-Framework]], you had to configure it using Scala, even if the you have a Java project. Now it is possible to create custom `LoggerConfigurator` in both Java and Scala. To create a `LoggerConfigurator` in Java, you need to implement the given interface, for example, to configure Log4J: + +```java +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import org.slf4j.ILoggerFactory; +import play.Environment; +import play.LoggerConfigurator; +import play.Mode; +import play.api.PlayException; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.*; +import org.apache.logging.log4j.core.config.Configurator; + +public class JavaLog4JLoggerConfigurator implements LoggerConfigurator { + + private ILoggerFactory factory; + + @Override + public void init(File rootPath, Mode mode) { + Map properties = new HashMap<>(); + properties.put("application.home", rootPath.getAbsolutePath()); + + String resourceName = "log4j2.xml"; + URL resourceUrl = this.getClass().getClassLoader().getResource(resourceName); + configure(properties, Optional.ofNullable(resourceUrl)); + } + + @Override + public void configure(Environment env) { + Map properties = LoggerConfigurator.generateProperties(env, ConfigFactory.empty(), Collections.emptyMap()); + URL resourceUrl = env.resource("log4j2.xml"); + configure(properties, Optional.ofNullable(resourceUrl)); + } + + @Override + public void configure(Environment env, Config configuration, Map optionalProperties) { + // LoggerConfigurator.generateProperties enables play.logger.includeConfigProperties=true + Map properties = LoggerConfigurator.generateProperties(env, configuration, optionalProperties); + URL resourceUrl = env.resource("log4j2.xml"); + configure(properties, Optional.ofNullable(resourceUrl)); + } + + @Override + public void configure(Map properties, Optional config) { + try { + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + loggerContext.setConfigLocation(config.get().toURI()); + + factory = org.slf4j.impl.StaticLoggerBinder.getSingleton().getLoggerFactory(); + } catch (URISyntaxException ex) { + throw new PlayException( + "log4j2.xml resource was not found", + "Could not parse the location for log4j2.xml resource", + ex + ); + } + } + + @Override + public ILoggerFactory loggerFactory() { + return factory; + } + + @Override + public void shutdown() { + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(); + Configurator.shutdown(loggerContext); + } +} +``` + +> **Note**: this implementation is fully compatible with Scala version `LoggerConfigurator` and can even be used in Scala projects if necessary, which means that module creators can provide a Java or Scala implementation of LoggerConfigurator and they will be usable in both Java and Scala projects. + +## Separate Java Forms module and PlayMinimalJava plugin + +The [[Java forms|JavaForms]] functionality has been split out into a separate module. The forms functionality depends on a few Spring modules and the Hibernate validator, so if you are not using forms, you may wish to remove the Java forms module to avoid those unnecessary dependencies. + +This module is automatically included by the `PlayJava` plugin, but can be disabled by using the `PlayMinimalJava` plugin instead: + +``` +lazy val root = (project in file(".")) + .enablePlugins(PlayMinimalJava) +``` + +## Java Compile Time Components + +Just as in Scala, Play now has components to enable [[Java Compile Time Dependency Injection|JavaCompileTimeDependencyInjection]]. The components were created as interfaces that you should `implements` and they provide default implementations. There are components for all the types that could be injected when using [[Runtime Dependency Injection|JavaDependencyInjection]]. To create an application using Compile Time Dependency Injection, you just need to provide an implementation of `play.ApplicationLoader` that uses a custom implementation of `play.BuiltInComponents`, for example: + +```java +import play.routing.Router; +import play.ApplicationLoader; +import play.BuiltInComponentsFromContext; +import play.filters.components.HttpFiltersComponents; + +public class MyComponents extends BuiltInComponentsFromContext + implements HttpFiltersComponents { + + public MyComponents(ApplicationLoader.Context context) { + super(context); + } + + @Override + public Router router() { + return Router.empty(); + } +} +``` + +The `play.ApplicationLoader`: + +```java +import play.ApplicationLoader; + +public class MyApplicationLoader implements ApplicationLoader { + + @Override + public Application load(Context context) { + return new MyComponents(context).application(); + } + +} +``` + +And configure `MyApplicationLoader` as explained in [[Java Compile-Time Dependency Injection docs|JavaCompileTimeDependencyInjection#Application-entry-point]]. + +## Improved Form Handling I18N support + +The `MessagesApi` and `Lang` classes are used for internationalization in Play, and are required to display error messages in forms. + +In the past, putting together a form in Play has required [multiple steps](https://www.theguardian.com/info/developer-blog/2015/dec/30/how-to-add-a-form-to-a-play-application), and the creation of a `Messages` instance from a request was not discussed in the context of form handling. + +In addition, it was inconvenient to have a `Messages` instance passed through all template fragments when form handling was required, and `Messages` implicit support was provided directly through the controller trait. The I18N API has been refined with the addition of a `MessagesProvider` trait, implicits that are tied directly to requests, and the forms documentation has been improved. + +The [`MessagesActionBuilder`](api/scala/play/api/mvc/MessagesActionBuilder.html) has been added. This action builder provides a [`MessagesRequest`](api/scala/play/api/mvc/MessagesRequest.html), which is a [`WrappedRequest`](api/scala/play/api/mvc/WrappedRequest.html) that extends [`MessagesProvider`](api/scala/play/api/i18n/MessagesProvider.html), only a single implicit parameter needs to be made available to templates, and you don't need to extend `Controller` with `I18nSupport`. This is also useful because to use [[CSRF|ScalaCsrf]] with forms, both a `Request` (technically a `RequestHeader`) and a `Messages` object must be available to the template. + +```scala +class FormController @Inject()(messagesAction: MessagesActionBuilder, components: ControllerComponents) + extends AbstractController(components) { + + import play.api.data.Form + import play.api.data.Forms._ + + val userForm = Form( + mapping( + "name" -> text, + "age" -> number + )(UserData.apply)(UserData.unapply) + ) + + def index = messagesAction { implicit request: MessagesRequest[AnyContent] => + Ok(views.html.displayForm(userForm)) + } + + def post = ... +} +``` + +where `displayForm.scala.html` is defined as: + +```twirl +@(userForm: Form[UserData])(implicit request: MessagesRequestHeader) + +@import helper._ + +@helper.form(action = routes.FormController.post()) { + @CSRF.formField @* <- takes a RequestHeader *@ + @helper.inputText(userForm("name")) @* <- takes a MessagesProvider *@ + @helper.inputText(userForm("age")) @* <- takes a MessagesProvider *@ +} +``` + +For more information, please see [[ScalaI18N]]. + +### Testing Support + +Support for creating `MessagesApi` instances has been improved. Now, when you want to create a [`MessagesApi`](api/scala/play/api/i18n/MessagesApi.html) instance, you can create [`DefaultMessagesApi()`](api/scala/play/api/i18n/DefaultMessagesApi.html) or [`DefaultLangs()`](api/scala/play/api/i18n/DefaultLangs.html) with default arguments. If you want to specify test messages from configuration or from another source, you can pass in those values: + +```scala +val messagesApi: MessagesApi = { + val env = new Environment(new File("."), this.getClass.getClassLoader, Mode.Dev) + val config = Configuration.reference ++ Configuration.from(Map("play.i18n.langs" -> Seq("en", "fr", "fr-CH"))) + val langs = new DefaultLangsProvider(config).get + new DefaultMessagesApi(testMessages, langs) + } +``` + +## Future Timeout and Delayed Support + +Play's support for futures in asynchronous operations has been improved, using the `Futures` trait. + +You can use the [`play.libs.concurrent.Futures`](api/java/play/libs/concurrent/Futures.html) interface to wrap a `CompletionStage` in a non-blocking timeout: + +```java +class MyClass { + @Inject + public MyClass(Futures futures) { + this.futures = futures; + } + + CompletionStage callWithOneSecondTimeout() { + return futures.timeout(computePIAsynchronously(), Duration.ofSeconds(1)); + } +} +``` + +or use [`play.api.libs.concurrent.Futures`](api/scala/play/api/libs/concurrent/Futures.html) trait in the Scala API: + +```scala +import play.api.libs.concurrent.Futures._ + +class MyController @Inject()(cc: ControllerComponents)(implicit futures: Futures) extends AbstractController(cc) { + + def index = Action.async { + // withTimeout is an implicit type enrichment provided by importing Futures._ + intensiveComputation().withTimeout(1.seconds).map { i => + Ok("Got result: " + i) + }.recover { + case e: TimeoutException => + InternalServerError("timeout") + } + } +} +``` + +There is also a `delayed` method which only executes a `Future` after a specified delay, which works similarly to timeout. + +For more information, please see [[ScalaAsync]] or [[JavaAsync]]. + +## CustomExecutionContext and Thread Pool Sizing + +This class defines a custom execution context that delegates to an `akka.actor.ActorSystem`. It is very useful for situations in which the default execution context should not be used, for example if a database or blocking I/O is being used. Detailed information can be found in the [[ThreadPools]] page, but Play 2.6.x adds a `CustomExecutionContext` class that handles the underlying Akka dispatcher lookup. + +## Updated Templates with Preconfigured CustomExecutionContexts + +All of the Play example templates on [Play's download page](https://playframework.com/download#examples) that use blocking APIs (i.e. Anorm, JPA) have been updated to use custom execution contexts where appropriate. For example, going to https://github.com/playframework/play-java-jpa-example/ shows that the [JPAPersonRepository](https://github.com/playframework/play-java-jpa-example/blob/4f962bc/app/models/JPAPersonRepository.java) class takes a `DatabaseExecutionContext` that wraps all the database operations. + +For thread pool sizing involving JDBC connection pools, you want a fixed thread pool size matching the connection pool, using a thread pool executor. Following the advice in [HikariCP's pool sizing page](https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing), you should configure your JDBC connection pool to double the number of physical cores, plus the number of disk spindles. + +The dispatcher settings used here come from [Akka dispatcher](https://doc.akka.io/docs/akka/2.5/dispatchers.html?language=java): + +``` +# db connections = ((physical_core_count * 2) + effective_spindle_count) +fixedConnectionPool = 9 + +database.dispatcher { + executor = "thread-pool-executor" + throughput = 1 + thread-pool-executor { + fixed-pool-size = ${fixedConnectionPool} + } +} +``` + +### Defining a CustomExecutionContext in Scala + +To define a custom execution context, subclass [`CustomExecutionContext`](api/scala/play/api/libs/concurrent/CustomExecutionContext.html) with the dispatcher name: + +```scala +@Singleton +class DatabaseExecutionContext @Inject()(system: ActorSystem) + extends CustomExecutionContext(system, "database.dispatcher") +``` + +Then have the execution context passed in as an implicit parameter: + +```scala +class DatabaseService @Inject()(implicit executionContext: DatabaseExecutionContext) { + ... +} +``` + +### Defining a CustomExecutionContext in Java + +To define a custom execution context, subclass [`CustomExecutionContext`](api/java/play/libs/concurrent/CustomExecutionContext.html) with the dispatcher name: + +```java +import akka.actor.ActorSystem; +import play.libs.concurrent.CustomExecutionContext; + +public class DatabaseExecutionContext + extends CustomExecutionContext { + + @javax.inject.Inject + public DatabaseExecutionContext(ActorSystem actorSystem) { + // uses a custom thread pool defined in application.conf + super(actorSystem, "database.dispatcher"); + } +} +``` + +Then pass the JPA context in explicitly: + +```java +public class JPAPersonRepository implements PersonRepository { + + private final JPAApi jpaApi; + private final DatabaseExecutionContext executionContext; + + @Inject + public JPAPersonRepository(JPAApi jpaApi, DatabaseExecutionContext executionContext) { + this.jpaApi = jpaApi; + this.executionContext = executionContext; + } + + ... +} +``` + +## Play `WSClient` Improvements + +There are substantial improvements to Play `WSClient`. Play `WSClient` is now a wrapper around the standalone [play-ws](https://github.com/playframework/play-ws) implementation, which can be used outside of Play. In addition, the underlying libraries involved in [play-ws](https://github.com/playframework/play-ws) have been [shaded](https://github.com/sbt/sbt-assembly#shading), so that the Netty implementation used in it does not conflict with Spark, Play or any other library that uses a different version of Netty. + +Finally, there is now support for [HTTP Caching](https://tools.ietf.org/html/rfc7234) if a cache implementation is present. Using an HTTP cache means savings on repeated requests to backend REST services, and is especially useful when combined with resiliency features such as [`stale-on-error` and `stale-while-revalidate`](https://tools.ietf.org/html/rfc5861). + +For more details, please see [[WsCache]] and the [[WS Migration Guide|WSMigration26]]. + +## Play JSON improvements + +There are many improvements included in this release of the JSON library. + +### Ability to serialize tuples + +Now, tuples are able to be serialized by play-json, and there are `Reads` and `Writes` implementations in the implicit scope. Tuples are serialized to arrays, so `("foo", 2, "bar")` will render as `["foo", 2, "bar"]` in the JSON. + +### Scala.js support + +Play JSON 2.6.0 now supports Scala.js. You can add the dependency with: + +```scala +libraryDependencies += "com.typesafe.play" %%% "play-json" % version +``` + +where `version` is the version you wish to use. The library should effectively work the same as it does on the JVM, except without support for JVM types. + +### Custom naming strategies for automated JSON mapping + +It is possible to customize the handlers generated by the `Json` macros (`reads`, `writes` or `format`). Thus, a naming strategy can be defined to map the JSON fields as wanted. + +To use a custom naming strategy you need to define implicit instances for `JsonConfiguration` and `JsonNaming`. + +Two naming strategies are provided: the default one, using as-is the names of the class properties, and the `JsonNaming.SnakeCase` case one. + +A strategy other than the default one can be used as following: + +```scala +import play.api.libs.json._ + +implicit val config = JsonConfiguration(SnakeCase) + +implicit val userFormat: OFormat[PlayUser] = Json.format[PlayUser] +``` + +In addition, custom naming strategies can be implemented by providing a `JsonNaming` implementation. + +## Testing Improvements + +Some utility classes have been added to the `play.api.test` package in 2.6.x to make functional testing easier with dependency injected components. + +### Injecting + +There are many functional tests that use the injector directly through the implicit `app`: + +```scala +"test" in new WithApplication() { + val executionContext = app.injector.instanceOf[ExecutionContext] + ... +} +``` + +Now with the [`Injecting`](api/scala/play/api/test/Injecting.html) trait, you can elide this: + +```scala +"test" in new WithApplication() with Injecting { + val executionContext = inject[ExecutionContext] + ... +} +``` + +### StubControllerComponents + +The [`StubControllerComponentsFactory`](api/scala/play/api/test/StubControllerComponentsFactory.html) creates a stub [`ControllerComponents`](api/scala/play/api/mvc/ControllerComponents.html) that can be used for unit testing a controller: + +```scala +val controller = new MyController(stubControllerComponents()) +``` + +### StubBodyParser + +The [`StubBodyParserFactory`](api/scala/play/api/test/StubBodyParserFactory.html) creates a stub [`BodyParser`](api/scala/play/api/mvc/BodyParser.html) that can be used for unit testing content: + +```scala +val stubParser = stubBodyParser(AnyContent("hello")) +``` + +## File Upload Improvements + +Uploading files uses a `TemporaryFile` API which relies on storing files in a temporary filesystem, as specified in [[ScalaFileUpload]] / [[JavaFileUpload]], accessible through the `ref` attribute. + +Uploading files is an inherently dangerous operation, because unbounded file upload can cause the filesystem to fill up -- as such, the idea behind `TemporaryFile` is that it's only in scope at completion and should be moved out of the temporary file system as soon as possible. Any temporary files that are not moved are deleted. + +In 2.5.x, TemporaryFile were deleted as the file references were garbage collected, using `finalize`. However, under [certain conditions](https://github.com/playframework/playframework/issues/5545), garbage collection did not occur in a timely fashion. The background cleanup has been moved to use [FinalizableReferenceQueue](https://google.github.io/guava/releases/20.0/api/docs/com/google/common/base/FinalizableReferenceQueue.html) and PhantomReferences rather than use `finalize`. + +The Java and Scala APIs for `TemporaryFile` has been reworked so that all `TemporaryFile` references come from a `TemporaryFileCreator` trait, and the implementation can be swapped out as necessary, and there's now an [`atomicMoveWithFallback`](api/scala/play/api/libs/Files$$TemporaryFile.html#atomicMoveWithFallback\(to:java.nio.file.Path\):play.api.libs.Files.TemporaryFile) method that uses `StandardCopyOption.ATOMIC_MOVE` if available. + +### TemporaryFileReaper + +There's also now a [`play.api.libs.Files.TemporaryFileReaper`](api/scala/play/api/libs/Files$$DefaultTemporaryFileReaper.html) that can be enabled to delete temporary files on a scheduled basis using the Akka scheduler, distinct from the garbage collection method. + +The reaper is disabled by default, and is enabled through `application.conf`: + +``` +play.temporaryFile { + reaper { + enabled = true + initialDelay = "5 minutes" + interval = "30 seconds" + olderThan = "30 minutes" + } +} +``` + +The above configuration will delete files that are more than 30 minutes old, using the "olderThan" property. It will start the reaper five minutes after the application starts, and will check the filesystem every 30 seconds thereafter. The reaper is not aware of any existing file uploads, so protracted file uploads may run into the reaper if the system is not carefully configured. diff --git a/manual/releases/release26/index.toc b/manual/releases/release26/index.toc new file mode 100644 index 00000000..efbc2ec5 --- /dev/null +++ b/manual/releases/release26/index.toc @@ -0,0 +1,2 @@ +Highlights26:What's new? +!migration26:Migration Guides diff --git a/manual/releases/release26/migration26/CacheMigration26.md b/manual/releases/release26/migration26/CacheMigration26.md new file mode 100644 index 00000000..210732a0 --- /dev/null +++ b/manual/releases/release26/migration26/CacheMigration26.md @@ -0,0 +1,84 @@ + +# Cache APIs Migration + +## New packages + +Now `cache` has been split into a `cacheApi` component with just the API, and `ehcache` that contains the Ehcache implementation. If you are using the default Ehcache implementation, simply change `cache` to `ehcache` in your `build.sbt`: + +``` +libraryDependencies ++= Seq( + ehcache +) +``` + +If you are defining a custom cache API, or are writing a cache implementation module, you can just depend on the API: + +``` +libraryDependencies ++= Seq( + cacheApi +) +``` + +## Removed APIs + +The deprecated Java class `play.cache.Cache` was removed and you now must inject an `play.cache.SyncCacheApi` or `play.cache.AsyncCacheApi`. + +## New Sync and Async Cache APIs + +The Cache API has been rewritten to have a synchronous and an asynchronous version. The old APIs will still work but they are now deprecated. + +### Java API + +The interface `play.cache.CacheApi` is now deprecated and should be replaced by `play.cache.SyncCacheApi` or `play.cache.AsyncCacheApi`. + +To use, `play.cache.SyncCacheApi` just inject it: + +```java +public class SomeController extends Controller { + + private SyncCacheApi cacheApi; + + @Inject + public SomeController(SyncCacheApi cacheApi) { + this.cacheApi = cacheApi; + } +} +``` + +And then there is the asynchronous version of the API: + +```java +public class SomeController extends Controller { + + private AsyncCacheApi cacheApi; + + @Inject + public SomeController(AsyncCacheApi cacheApi) { + this.cacheApi = cacheApi; + } +} +``` + +See more details about how to use both APIs at [[specific documentation|JavaCache]]. + +### Scala API + +The trait `play.api.cache.CacheApi` is now deprecated and should be replaced by `play.api.cache.SyncCacheApi` or `play.api.cache.AsyncCacheApi`. + +To use `play.api.cache.SyncCacheApi`, just inject it: + +```scala +class Application @Inject() (cache: SyncCacheApi) extends Controller { + +} +``` + +Basically the same for `play.api.cache.AsyncCacheApi`: + +```scala +class Application @Inject() (cache: AsyncCacheApi) extends Controller { + +} +``` + +See more details about how to use both APIs at [[specific documentation|ScalaCache]]. diff --git a/manual/releases/release26/migration26/JPAMigration26.md b/manual/releases/release26/migration26/JPAMigration26.md new file mode 100644 index 00000000..a348d1b9 --- /dev/null +++ b/manual/releases/release26/migration26/JPAMigration26.md @@ -0,0 +1,25 @@ + +# JPA Migration + +## Removed Deprecated Methods + +The following deprecated methods have been removed in Play 2.6. + +* `play.db.jpa.JPA.jpaApi` +* `play.db.jpa.JPA.em(key)` +* `play.db.jpa.JPA.bindForAsync(em)` +* `play.db.jpa.JPA.withTransaction` + +Please use a `JPAApi` injected instance as specified in [[Using play.db.jpa.JPAApi|JavaJPA#Using-play.db.jpa.JPAApi]]. + +## Deprecated JPA Class + +As of 2.6.1, the `play.db.jpa.JPA` class has been deprecated, as it uses global state under the hood. The deprecation was mistakenly left out of 2.6.0. + +Please use a `JPAApi` injected instance as specified in [[Using play.db.jpa.JPAApi|JavaJPA#Using-play.db.jpa.JPAApi]]. + +## Added Async Warning + +Added the following to [[JavaJPA]]: + +> Using JPA directly in an Action will limit your ability to use Play asynchronously. Consider arranging your code so that all access to to JPA is wrapped in a custom [[execution context|ThreadPools]], and returns [`java.util.concurrent.CompletionStage`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html) to Play. diff --git a/manual/releases/release26/migration26/JavaConfigMigration26.md b/manual/releases/release26/migration26/JavaConfigMigration26.md new file mode 100644 index 00000000..62101734 --- /dev/null +++ b/manual/releases/release26/migration26/JavaConfigMigration26.md @@ -0,0 +1,96 @@ + + +# Java Configuration API Migration + +The class `play.Configuration` was deprecated in favor of using [Typesafe Config](https://github.com/typesafehub/config) directly. So, instead of using `play.Configuration` you must now use [`com.typesafe.config.Config`](https://lightbend.github.io/config/latest/api/com/typesafe/config/Config.html). For example: + +Before: +```java +import play.Configuration; +public class Foo { + private final Configuration configuration; + + @javax.inject.Inject + public Foo(Configuration configuration) { + this.configuration = configuration; + } +} +``` + +After: +```java +import com.typesafe.config.Config; + +public class Foo { + private final Config config; + + @javax.inject.Inject + public Foo(Config config) { + this.config = config; + } +} +``` + +## Config values should always be defined + +The main difference between the `Config` and `play.Configuration` APIs is how to handle default values. [Typesafe Config advocates](https://github.com/typesafehub/config/tree/v1.3.1#how-to-handle-defaults) that all configuration keys must be declared in your `.conf` files, including the default values. + +Play itself is using `reference.conf` files to declare default values for all the possible configurations. To avoid the hassle of handling missing values, you can do the same if you are distributing a library. When the configuration is read, the `application.conf` files are layered on top of the `reference.conf` configuration. For example: + +Before (`configuration` is `play.Configuration`): +```java +// Here we have the default values inside the code, which is not the idiomatic way when using Typesafe Config. +Long timeout = configuration.getMilliseconds("my.service.timeout", 5000); // 5 seconds +``` + +After: +``` +# This is declared in `conf/reference.conf`. +my.service.timeout = 5 seconds +``` + +And you can eventually override the value in your `application.conf` file: + +``` +# This will override the value declared in reference.conf +my.service.timeout = 10 seconds +``` + +This is especially useful when creating modules, since your module can provide reference values that are easy to override. Your Java code will then look like: + +```java +Long timeout = config.getDuration("my.service.timeout", TimeUnit.MILLISECONDS); +``` + +where `config` is your `com.typesafe.config.Config` instance. + +## Manually checking values + +If you don't want or if you cannot have default values for some reason, you can use [`Config.hasPath`](https://lightbend.github.io/config/latest/api/com/typesafe/config/Config.html#hasPath-java.lang.String-) or [`Config.hasPathOrNull`](https://lightbend.github.io/config/latest/api/com/typesafe/config/Config.html#hasPathOrNull-java.lang.String-) to check if the value is configured before accessing it. This is a better option if the configuration is required but you can't provide a reference (default) value: + +```java +import com.typesafe.config.Config; +import com.typesafe.config.ConfigException; + +public class EmailServerConfig { + + private static final String SERVER_ADDRESS_KEY = "my.smtp.server.address"; + + private final Config config; + + @javax.inject.Inject + public EmailServerConfig(Config config) { + this.config = config; + } + + // The relevant code is here. First use `hasPath` to check if the configuration + // exists and, if not, throw an exception. + public String getSmtpAddress() { + if (config.hasPath(SERVER_ADDRESS_KEY)) { + return config.getString(SERVER_ADDRESS_KEY); + } else { + throw new ConfigException.Missing(SERVER_ADDRESS_KEY); + } + } +} +``` diff --git a/manual/releases/release26/migration26/MessagesMigration26.md b/manual/releases/release26/migration26/MessagesMigration26.md new file mode 100644 index 00000000..efa26769 --- /dev/null +++ b/manual/releases/release26/migration26/MessagesMigration26.md @@ -0,0 +1,320 @@ + +# I18N API Migration + +There are a number of changes to the I18N API to make working with messages and languages easier to use, particularly with forms and templates. + +## Java API + +### Refactored Messages API to interfaces + +The `play.i18n` package has changed to make access to [`Messages`](api/java/play/i18n/Messages.html) easier. These changes should be transparent to the user, but are provided here for teams extending the I18N API. + +[`Messages`](api/java/play/i18n/Messages.html) is now an interface, and there is a [`MessagesImpl`](api/java/play/i18n/MessagesImpl.html) class that implements that interface. + +### Deprecated / Removed Methods + +The static deprecated methods in [`play.i18n.Messages`](api/java/play/i18n/Messages.html) have been removed in 2.6.x, as there are equivalent methods on the [`MessagesApi`](api/java/play/i18n/MessagesApi.html) instance. + +## Scala API + +### Removed Implicit Default Lang + +The [`Lang`](api/scala/play/api/i18n/Lang.html) singleton object has a `defaultLang` that points to the JVM default Locale. Pre 2.6.x, `defaultLang` was an implicit value, with the result that it could be used in implicit scope resolution if no `Lang` was found in local scope. This setting was too general and resulted in bugs where `defaultLang` was being used instead of a request's locale, if the request was not declared as implicit. + +As a result, the implicit has been removed, and so what was: + +```scala +object Lang { + implicit lazy val defaultLang: Lang = Lang(java.util.Locale.getDefault) +} +``` + +is now: + +```scala +object Lang { + lazy val defaultLang: Lang = Lang(java.util.Locale.getDefault) +} +``` + +Any code that was relying on this implicit should use `Lang.defaultLang` explicitly. + +### Refactored Messages API to traits + + The `play.api.i18n` package has changed to make access to [`Messages`](api/scala/play/api/i18n/Messages.html) instances easier and reduce the number of implicits in play. These changes should be transparent to the user, but are provided here for teams extending the I18N API. + +[`Messages`](api/scala/play/api/i18n/Messages.html) is now a trait (rather than a case class). The case class is now [`MessagesImpl`](api/scala/play/api/i18n/MessagesImpl.html), which implements [`Messages`](api/scala/play/api/i18n/Messages.html). + +### I18nSupport Implicit Conversion + +If you are upgrading directly from Play 2.5 to Play 2.6, you should know that `I18nSupport` support has changed in 2.6.x. In 2.5.x, it was possible through a series of implicits to use a "language default" `Messages` instance if the request was not declared to be in implicit scope: + +```scala + def listWidgets = Action { + val lang = implicitly[Lang] // Uses Lang.defaultLang + val messages = implicitly[Messages] // Uses I18nSupport.lang2messages(Lang.defaultLang) + // implicit parameter messages: Messages in requiresMessages template, but no request! + val content = views.html.requiresMessages(form) + Ok(content) + } +``` + +The [`I18nSupport`](api/scala/play/api/i18n/I18nSupport.html) implicit conversion now requires an implicit request or request header in scope in order to correctly determine the preferred locale and language for the request. + +This means if you have the following: + +```scala +def index = Action { + +} +``` + +You need to change it to: + +```scala +def index = Action { implicit request => + +} +``` + +This will allow i18n support to see the request's locale and provide error messages and validation alerts in the user's language. + +### Smoother I18nSupport + +Using a form inside a controller is a smoother experience in 2.6.x. [`ControllerComponents`](api/scala/play/api/mvc/ControllerComponents.html) contains a [`MessagesApi`](api/scala/play/api/i18n/MessagesApi.html) instance, which is exposed by [`AbstractController`](api/scala/play/api/mvc/AbstractController.html). This means that the [`I18nSupport`](api/scala/play/api/i18n/I18nSupport.html) trait does not require an explicit `val messagesApi: MessagesApi` declaration, as it did in Play 2.5.x. + +```scala +class FormController @Inject()(components: ControllerComponents) + extends AbstractController(components) with I18nSupport { + + import play.api.data.validation.Constraints._ + + val userForm = Form( + mapping( + "name" -> text.verifying(nonEmpty), + "age" -> number.verifying(min(0), max(100)) + )(UserData.apply)(UserData.unapply) + ) + + def index = Action { implicit request => + // use request2messages implicit conversion method + Ok(views.html.user(userForm)) + } + + def showMessage = Action { request => + // uses type enrichment + Ok(request.messages("hello.world")) + } + + def userPost = Action { implicit request => + userForm.bindFromRequest.fold( + formWithErrors => { + BadRequest(views.html.user(formWithErrors)) + }, + user => { + Redirect(routes.FormController.index()).flashing("success" -> s"User is ${user}!") + } + ) + } +} +``` + +Note there is now also type enrichment in [`I18nSupport`](api/scala/play/api/i18n/I18nSupport.html) which adds `request.messages` and `request.lang`. This can be added either by extending from [`I18nSupport`](api/scala/play/api/i18n/I18nSupport.html), or by `import I18nSupport._`. The import version does not contain the `request2messages` implicit conversion. + +## Integrated Messages with MessagesProvider + +A new [`MessagesProvider`](api/scala/play/api/i18n/MessagesProvider.html) trait is available, which exposes a [`Messages`](api/scala/play/api/i18n/Messages.html) instance. + +```scala +trait MessagesProvider { + def messages: Messages +} +``` + +[`MessagesImpl`](api/scala/play/api/i18n/MessagesImpl.html) implements [`Messages`](api/scala/play/api/i18n/Messages.html) and [`MessagesProvider`](api/scala/play/api/i18n/MessagesProvider.html), and returns itself by default. + +All the template helpers now take [`MessagesProvider`](api/scala/play/api/i18n/MessagesProvider.html) as an implicit parameter, rather than a straight `Messages` object, i.e. `inputText.scala.html` takes the following: + +```scala +@(field: play.api.data.Field, args: (Symbol,Any)*)(implicit handler: FieldConstructor, messagesProvider: play.api.i18n.MessagesProvider) +``` + +The benefit to using a [`MessagesProvider`](api/scala/play/api/i18n/MessagesProvider.html) is that otherwise, if you used implicit `Messages`, you would have to introduce implicit conversions from other types like `Request` in places where those implicits could be confusing. + +### MessagesRequest and MessagesAbstractController + +To assist, there's [`MessagesRequest`](api/scala/play/api/mvc/MessagesRequest.html), which is a [`WrappedRequest`](api/scala/play/api/mvc/WrappedRequest.html) that implements [`MessagesProvider`](api/scala/play/api/i18n/MessagesProvider.html) and provides the preferred language. + +You can access a [`MessagesRequest`](api/scala/play/api/mvc/MessagesRequest.html) by using a [`MessagesActionBuilder`](api/scala/play/api/mvc/MessagesActionBuilder.html): + +```scala + +class MyController @Inject()( + messagesAction: MessagesActionBuilder, + cc: ControllerComponents + ) extends AbstractController(cc) { + def index = messagesAction { implicit request: MessagesRequest[AnyContent] => + Ok(views.html.formTemplate(form)) // twirl template with form builders + } +} + +``` + +Or you can use [`MessagesAbstractController`](api/scala/play/api/mvc/MessagesAbstractController.html), which swaps out the default `Action` that provides `MessagesRequest` instead of `Request` in the block: + +```scala + +class MyController @Inject() ( + mcc: MessagesControllerComponents +) extends MessagesAbstractController(mcc) { + + def index = Action { implicit request: MessagesRequest[AnyContent] => + Ok(s"The messages are ${request.messages}") + } +} + +``` + +Here's a complete example using a form with a CSRF action (assuming that you have CSRF filter disabled): + +```scala + +class MyController @Inject() ( + addToken: CSRFAddToken, + checkToken: CSRFCheck, + mcc: MessagesControllerComponents +) extends MessagesAbstractController(mcc) { + + import play.api.data.Form + import play.api.data.Forms._ + + val userForm = Form( + mapping( + "name" -> text, + "age" -> number + )(UserData.apply)(UserData.unapply) + ) + + def index = addToken { + Action { implicit request => + Ok(views.html.formpage(userForm)) + } + } + + def userPost = checkToken { + Action { implicit request => + userForm.bindFromRequest.fold( + formWithErrors => { + play.api.Logger.info(s"unsuccessful user submission") + BadRequest(views.html.formpage(formWithErrors)) + }, + user => { + play.api.Logger.info(s"successful user submission ${user}") + Redirect(routes.MyController.index()).flashing("success" -> s"User is ${user}!") + } + ) + } + } +} + +``` + +Because `MessagesRequest` is a `MessagesProvider`, you only have to define the request as implicit and it will carry through to the template. This is especially useful when CSRF checks are involved. The `formpage.scala.html` page is as follow: + +```scala + +@(userForm: Form[UserData])(implicit request: MessagesRequestHeader) + +@helper.form(action = routes.MyController.userPost()) { + @views.html.helper.CSRF.formField + @helper.inputText(userForm("name")) + @helper.inputText(userForm("age")) + +} + +``` + +Note that because the body of the `MessageRequest` is not relevant to the template, we can use `MessagesRequestHeader` here instead of `MessageRequest[_]`. + +Please see [[passing messages to form helpers|ScalaForms#Passing-MessagesProvider-to-Form-Helpers]] for more details. + +### DefaultMessagesApi component + +The default implementation of [`MessagesApi`](api/scala/play/api/i18n/MessagesApi.html) is [`DefaultMessagesApi`](api/scala/play/api/i18n/DefaultMessagesApi.html). [`DefaultMessagesApi`](api/scala/play/api/i18n/DefaultMessagesApi.html) used to take [`Configuration`](api/scala/play/api/Configuration.html) and [`Environment`](api/scala/play/api/Environment.html) directly, which made it awkward to deal with in forms. For unit testing purposes, [`DefaultMessagesApi`](api/scala/play/api/i18n/DefaultMessagesApi.html) can be instantiated without arguments, and will take a raw map. + +```scala + +import play.api.data.Forms._ +import play.api.data._ +import play.api.i18n._ + +val messagesApi = new DefaultMessagesApi( + Map("en" -> + Map("error.min" -> "minimum!") + ) +) +implicit val request = { + play.api.test.FakeRequest("POST", "/") + .withFormUrlEncodedBody("name" -> "Play", "age" -> "-1") +} +implicit val messages = messagesApi.preferred(request) + +def errorFunc(badForm: Form[UserData]) = { + BadRequest(badForm.errorsAsJson) +} + +def successFunc(userData: UserData) = { + Redirect("/").flashing("success" -> "success form!") +} + +val result = Future.successful(form.bindFromRequest().fold(errorFunc, successFunc)) +Json.parse(contentAsString(result)) must beEqualTo(Json.obj("age" -> Json.arr("minimum!"))) + +``` + +For functional tests that involve configuration, the best option is to use `WithApplication` and pull in an injected [`MessagesApi`](api/scala/play/api/i18n/MessagesApi.html): + +```scala + +import play.api.test.{ PlaySpecification, WithApplication } +import play.api.i18n._ + +class MessagesSpec extends PlaySpecification { + + sequential + + implicit val lang = Lang("en-US") + + "Messages" should { + "provide default messages" in new WithApplication(_.requireExplicitBindings()) { + val messagesApi = app.injector.instanceOf[MessagesApi] + val javaMessagesApi = app.injector.instanceOf[play.i18n.MessagesApi] + + val msg = messagesApi("constraint.email") + val javaMsg = javaMessagesApi.get(new play.i18n.Lang(lang), "constraint.email") + + msg must ===("Email") + msg must ===(javaMsg) + } + "permit default override" in new WithApplication(_.requireExplicitBindings()) { + val messagesApi = app.injector.instanceOf[MessagesApi] + val msg = messagesApi("constraint.required") + + msg must ===("Required!") + } + } +} + +``` + +If you need to customize the configuration, it's better to add configuration values into the [`GuiceApplicationBuilder`](api/scala/play/api/inject/guice/GuiceApplicationBuilder.html) rather than use the [`DefaultMessagesApiProvider`](api/scala/play/api/i18n/DefaultMessagesApiProvider.html) directly. + +### Deprecated Methods + +`play.api.i18n.Messages.Implicits.applicationMessagesApi` and `play.api.i18n.Messages.Implicits.applicationMessages` have been deprecated, because they rely on an implicit `Application` instance. + +The `play.api.mvc.Controller.request2lang` method has been deprecated, because it was using a global `Application` under the hood. + +The `play.api.i18n.I18nSupport.request2Messages` implicit conversion method has been moved to `I18NSupportLowPriorityImplicits.request2Messages`, and deprecated in favor of `request.messages` type enrichment, which is clearer overall. + +The `I18NSupportLowPriorityImplicits.lang2Messages` implicit conversion has been moved out to `LangImplicits.lang2Messages`, because of confusion when both implicit Request and a Lang were in scope. Please extend the [`play.api.i18n.LangImplicits`](api/scala/play/api/i18n/LangImplicits.html) trait specifically if you want to create a `Messages` from an implicit `Lang`. diff --git a/manual/releases/release26/migration26/Migration26.md b/manual/releases/release26/migration26/Migration26.md new file mode 100644 index 00000000..d9eb015c --- /dev/null +++ b/manual/releases/release26/migration26/Migration26.md @@ -0,0 +1,1241 @@ + +# Play 2.6 Migration Guide + +This is a guide for migrating from Play 2.5 to Play 2.6. If you need to migrate from an earlier version of Play then you must first follow the [[Play 2.5 Migration Guide|Migration25]]. + +## How to migrate + +The following steps need to be taken to update your sbt build before you can load/run a Play project in sbt. + +### Play upgrade + +Update the Play version number in `project/plugins.sbt` to upgrade Play: + +```scala +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.x") +``` + +Where the "x" in `2.6.x` is the minor version of Play you want to use, for instance `2.6.0`. + +### sbt upgrade to 0.13.15 + +Play 2.6 requires upgrading to at least sbt 0.13.15. The 0.13.15 release of sbt has a number of [improvements and bug fixes](https://www.scala-sbt.org/0.13/docs/sbt-0.13-Tech-Previews.html#sbt+0.13.15) (see also the changes in [sbt 0.13.13](https://www.scala-sbt.org/0.13/docs/sbt-0.13-Tech-Previews.html#sbt+0.13.13)). + +sbt 1.x is supported beginning with Play 2.6.6. If you are using other sbt plugins, you may need to check if there is a newer version compatible with sbt 1.x + +To update, change your `project/build.properties` so that it reads: + +``` +sbt.version=0.13.15 +``` + +### Guice DI support moved to separate module + +In Play 2.6, the core Play module no longer includes Guice. You will need to configure the Guice module by adding `guice` to your `libraryDependencies`: + +```scala +libraryDependencies += guice +``` + +### OpenID support moved to separate module + +In Play 2.6, the core Play module no longer includes the OpenID support in `play.api.libs.openid` (Scala) and `play.libs.openid` (Java). To use these packages add `openId` to your `libraryDependencies`: + +```scala +libraryDependencies += openId +``` + +### Play JSON moved to separate project + +Play JSON has been moved to a separate library hosted at https://github.com/playframework/play-json. Since Play JSON has no dependencies on the rest of Play, the main change is that the `json` value from `PlayImport` will no longer work in your sbt build. Instead, you'll have to specify the library manually: + +```scala +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.0" +``` + +Also, play-json has a separate release cycle from the core Play library, so the version is no longer in sync with the Play version. + +### Play Iteratees moved to separate project + +Play Iteratees has been moved to a separate library hosted at https://github.com/playframework/play-iteratees. Since Play Iteratees has no dependencies on the rest of Play, the main change is that the you'll have to specify the library manually: + +```scala +libraryDependencies += "com.typesafe.play" %% "play-iteratees" % "2.6.1" +``` + +The project also has a sub project that integrates Iteratees with [Reactive Streams](http://www.reactive-streams.org/). You may need to add the following dependency as well: + +```scala +libraryDependencies += "com.typesafe.play" %% "play-iteratees-reactive-streams" % "2.6.1" +``` + +> **Note**: The helper class `play.api.libs.streams.Streams` was moved to `play-iteratees-reactive-streams` and now is called `play.api.libs.iteratee.streams.IterateeStreams`. So you may need to add the Iteratees dependencies and also use the new class where necessary. + +Finally, Play Iteratees has a separate versioning scheme, so the version is no longer in sync with the Play version. + +## Akka HTTP as the default server engine + +Play now uses the [Akka-HTTP](https://doc.akka.io/docs/akka-http/current/?language=scala) server engine as the default backend. If you need to change it back to Netty for some reason (for example, if you are using Netty's [native transports](https://netty.io/wiki/native-transports.html)), see how to do that in [[Netty Server|NettyServer]] documentation. + +You can read more at [[Akka HTTP Server Backend|AkkaHttpServer]]. + +### Akka HTTP server timeouts + +Play 2.5.x does not have a request timeout configuration for [[Netty Server|NettyServer]], which was the default server backend. But Akka HTTP has timeouts for both idle connections and requests (see more details in [[Akka HTTP Settings|SettingsAkkaHttp]] documentation). [Akka HTTP docs](https://doc.akka.io/docs/akka-http/current/common/timeouts.html?language=scala#akka-http-timeouts) states that: + +> Akka HTTP comes with a variety of built-in timeout mechanisms to protect your servers from malicious attacks or programming mistakes. + +And you can see the default values for `akka.http.server.idle-timeout`, `akka.http.server.request-timeout` and `akka.http.server.bind-timeout` [here](https://doc.akka.io/docs/akka-http/current/configuration.html?language=scala). Play has [[its own configurations to define timeouts|SettingsAkkaHttp]], so if you start to see a number of `503 Service Unavailable`, you can change the configurations to values that are more reasonable to your application, for example: + +``` +play.server.http.idleTimeout = 60s +play.server.akka.requestTimeout = 40s +``` + +## Scala `Mode` changes + +Scala [`Mode`](api/scala/play/api/Mode.html) was refactored from an Enumeration to a hierarchy of case objects. Most of the Scala code won't change because of this refactoring. But, if you are accessing the Scala `Mode` values in your Java code, you will need to change it from: + +```java +// Consider this Java code +play.api.Mode scalaMode = play.api.Mode.Test(); +``` + +Must be rewritten to: + +```java +// Consider this Java code +play.api.Mode scalaMode = play.Mode.TEST.asScala(); +``` + +It is also easier to convert between Java and Scala modes: + +```java +// In your Java code +play.api.Mode scalaMode = play.Mode.DEV.asScala(); +``` + +Or in your Scala code: + +```scala +play.Mode javaMode = play.api.Mode.Dev.asJava +``` + +Also, `play.api.Mode.Mode` is now deprecated and you should use `play.api.Mode` instead. + +## `Writeable[JsValue]` changes + +Previously, the default Scala `Writeable[JsValue]` allowed you to define an implicit `Codec`, which would allow you to write using a different charset. This could be a problem since `application/json` does not act like text-based content types. It only allows Unicode charsets (`UTF-8`, `UTF-16` and `UTF-32`) and does not define a `charset` parameter like many text-based content types. + +Now, the default `Writeable[JsValue]` takes no implicit parameters and always writes to `UTF-8`. This covers the majority of cases, since most users want to use UTF-8 for JSON. It also allows us to easily use more efficient built-in methods for writing JSON to a byte array. + +If you need the old behavior back, you can define a `Writeable` with an arbitrary codec using `play.api.http.Writeable.writeableOf_JsValue(codec, contentType)` for your desired Codec and Content-Type. + +## Scala Controller changes + +The idiomatic Play controller has in the past required global state. The main places that was needed was in the global [`Action`](api/scala/play/api/mvc/Action$.html) object and [`BodyParsers#parse`](api/scala/play/api/mvc/BodyParsers.html#parse:play.api.mvc.PlayBodyParsers) method. + +We have provided several new controller classes with new ways of injecting that state, providing the same syntax: + - [`BaseController`](api/scala/play/api/mvc/BaseController.html): a trait with an abstract [`ControllerComponents`](api/scala/play/api/mvc/ControllerComponents.html) that can be provided by an implementing class. + - [`AbstractController`](api/scala/play/api/mvc/AbstractController.html): an abstract class extending [`BaseController`](api/scala/play/api/mvc/BaseController.html) with a [`ControllerComponents`](api/scala/play/api/mvc/ControllerComponents.html) constructor parameter that can be injected using constructor injection. + - [`InjectedController`](api/scala/play/api/mvc/InjectedController.html): a trait, extending [`BaseController`](api/scala/play/api/mvc/BaseController.html), that obtains the [`ControllerComponents`](api/scala/play/api/mvc/ControllerComponents.html) through method injection (calling a `setControllerComponents` method). If you are using a runtime DI framework like Guice, this is done automatically. + +[`ControllerComponents`](api/scala/play/api/mvc/ControllerComponents.html) is simply meant to bundle together components typically used in a controller. You may also wish to create your own base controller for your app by extending [`ControllerHelpers`](api/scala/play/api/mvc/ControllerHelpers.html) and injecting your own bundle of components. Play does not require your controllers to implement any particular trait. + +Note that [`BaseController`](api/scala/play/api/mvc/BaseController.html) makes [`Action`](api/scala/play/api/mvc/Action.html) and `parse` refer to injected instances rather than the global objects, which is usually what you want to do. + +Here's an example of code using [`AbstractController`](api/scala/play/api/mvc/AbstractController.html): + +```scala +class FooController @Inject() (components: ControllerComponents) + extends AbstractController(components) { + + // Action and parse now use the injected components + def foo = Action(parse.json) { + Ok + } +} +``` + +and using [`BaseController`](api/scala/play/api/mvc/BaseController.html): + +```scala +class FooController @Inject() (val controllerComponents: ControllerComponents) extends BaseController { + + // Action and parse now use the injected components + def foo = Action(parse.json) { + Ok + } +} +``` + +and [`InjectedController`](api/scala/play/api/mvc/InjectedController.html): + +```scala +class FooController @Inject() () extends InjectedController { + + // Action and parse now use the injected components + def foo = Action(parse.json) { + Ok + } +} +``` + +[`InjectedController`](api/scala/play/api/mvc/InjectedController.html) gets its [`ControllerComponents`](api/scala/play/api/mvc/ControllerComponents.html) by calling the `setControllerComponents` method, which is called automatically by JSR-330 compliant dependency injection. We do not recommend using [`InjectedController`](api/scala/play/api/mvc/InjectedController.html) with compile-time injection. If you plan to extensively unit test your controllers manually, we also recommend avoiding [`InjectedController`](api/scala/play/api/mvc/InjectedController.html) since it hides the dependency. + +If you prefer to pass the individual dependencies manually, you can do that instead and extend [`ControllerHelpers`](api/scala/play/api/mvc/ControllerHelpers.html), which has no dependencies or state. Here's an example: + +```scala +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 and BodyParser changes + +The Scala [`ActionBuilder`](api/scala/play/api/mvc/ActionBuilder.html) trait has been modified to specify the type of the body as a type parameter, and add an abstract `parser` member as the default body parsers. You will need to modify your ActionBuilders and pass the body parser directly. + +The [`Action`](api/scala/play/api/mvc/Action$.html) global object and [`BodyParsers#parse`](api/scala/play/api/mvc/BodyParsers.html#parse:play.api.mvc.PlayBodyParsers) are now deprecated. They are replaced by injectable traits, [`DefaultActionBuilder`](api/scala/play/api/mvc/DefaultActionBuilder.html) and [`PlayBodyParsers`](api/scala/play/api/mvc/PlayBodyParsers.html) respectively. If you are inside a controller, they are automatically provided by the new [`BaseController`](api/scala/play/api/mvc/BaseController.html) trait (see [the controller changes](#Scala-Controller-changes) above). + +## Cookies + +For Java users, we now recommend using [`play.mvc.Http.Cookie.builder`](api/java/play/mvc/Http.Cookie.html#builder-java.lang.String-java.lang.String-) to create new cookies, for example: + +```java +Http.Cookie cookie = Cookie.builder("color", "blue") + .withMaxAge(3600) + .withSecure(true) + .withHttpOnly(true) + .withSameSite(SameSite.STRICT) + .build(); +``` + +This is more readable than a plain constructor call, and will be source-compatible if we add/remove cookie attributes in the future. + +### SameSite attribute, enabled for session and flash + +Cookies now can have an additional [`SameSite` attribute](https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00), which can be used to prevent CSRF. There are three possible states: + + - No `SameSite`, meaning cookies will be sent for all requests to that domain. + - `SameSite=Strict`, meaning the cookie will only be sent for same-site requests (coming from another page on the site) not cross-site requests + - `SameSite=Lax`, meaning the cookie will be sent for cross-site requests as top-level navigation, but otherwise only for same-site requests. This will do the correct thing for most sites, but won't prevent certain types of attacks, such as those executed by launching popup windows. + +In addition, we have moved the session and flash cookies to use `SameSite=Lax` by default. You can tweak this using configuration. For example: + +``` +play.http.session.sameSite = null // no same-site for session +play.http.flash.sameSite = "strict" // strict same-site for flash +``` + +> **Note**: this feature is currently [not supported by many browsers](https://caniuse.com/#feat=same-site-cookie-attribute), so you should not rely on it. Chrome and Opera are the only major browsers to support SameSite right now. + +### __Host and __Secure prefixes + +We've also added support for the [\__Host and \__Secure cookie name prefixes](https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3). + +This will only affect you if you happen to be using these prefixes for cookie names. If you are, Play will warn when serializing and deserializing those cookies if the proper attributes are not set, then set them for you automatically. To remove the warning, either cease using those prefixes for your cookies, or be sure to set the attributes as follows: + +- Cookies named with `__Host-` should set `Path=/` and `Secure` attributes. +- Cookies named with `__Secure-` should set the `Secure` attribute. + +## Assets + +### Binding Assets with compile-time DI + +If you are using compile-time DI, you should mix in [`controllers.AssetsComponents`](api/scala/controllers/AssetsComponents.html) and use that to obtain the `assets: Assets` controller instance: + +```scala +class MyComponents(context: Context) extends BuiltInComponentsFromContext(context) with AssetsComponents { + lazy val router = new Routes(httpErrorHandler, assets) +} +``` + +If you have an existing `lazy val assets: Assets` you can remove it. + +### Assets configuration + +Existing user-facing APIs have not changed, but we suggest moving over to the [`AssetsFinder`](api/scala/controllers/AssetsFinder.html) API for finding assets and setting up your assets directories in configuration: + +``` +play.assets { + path = "/public" + urlPrefix = "/assets" +} +``` + +Then in routes you can do: + +``` +# prefix must match `play.assets.urlPrefix` +GET /assets/*file controllers.Assets.at(file) +GET /versionedAssets/*file controllers.Assets.versioned(file) +``` + +You no longer need to provide an assets path at the start of the argument list, since that's now read from configuration. + +Then in your template you can use [`AssetsFinder#path`](api/scala/controllers/AssetsFinder.html#path\(rawPath:String\):String) to find the final path of the asset: + +```scala +@(assets: AssetsFinder) + +hamburger +``` + +You can still continue to use reverse routes with `Assets.versioned`, but some global state is required to convert the asset name you provide to the final asset name, which can be problematic if you want to run multiple applications at once. + +## Form changes + +Starting with Play 2.6, query string parameters will not be bound to a form instance anymore when using [`bindFromRequest()`](api/scala/play/api/data/Form.html#bindFromRequest\(\)\(implicitrequest:play.api.mvc.Request[_]\):play.api.data.Form[T]) in combination with `POST`, `PUT` or `PATCH` requests. + +Static methods which were already deprecated in 2.5 (e.g. `DynamicForm.form()`) were removed in this release. Refer to the [[Play 2.5 Migration Guide|Migration25]] for details on how to migrate, in case you still use them. + +### Java Form Changes + +The [`errors()`](api/java/play/data/Form.html#errors--) method of a [`play.data.Form`](api/java/play/data/Form.html) instance is now deprecated. You should use `allErrors()` instead now which returns a simple `List` instead of a `Map>`. Where before Play 2.6 you called `.errors().get("key")` you can now simply call `.errors("key")`. + +From now on, a `validate` method implemented inside a form class (usually used for cross field validation) is part of a class-level constraint. Check out the [[Advanced validation|JavaForms#advanced-validation]] docs for further information on how to use such constraints. +Existing `validate` methods can easily be migrated by annotating the affected form classes with `@Validate` and, depending on the return type of the validate method, by implementing the [`Validatable`](api/java/play/data/validation/Constraints.Validatable.html) interface with the applicable type argument (all defined in [`play.data.validation.Constraints`](api/java/play/data/validation/Constraints.html)): + +| **Return type** | **Interface to implement** +| -----------------------------------------------------------------------------------|------------------------------------- +| `String` | `Validatable` +| `ValidationError` | `Validatable` +| `List` | `Validatable>` +| `Map>`
(not supported anymore; use `List` instead) | `Validatable>` + +For example an existing form like: + +```java +public class MyForm { + //... + public String validate() { + //... + } +} +``` + +Has to be changed to: + +```java +import play.data.validation.Constraints.Validate; +import play.data.validation.Constraints.Validatable; + +@Validate +public class MyForm implements Validatable { + //... + @Override + public String validate() { + //... + } +} +``` + +> **Be aware**: The "old" `validate` method was invoked only after all other constraints were successful before. By default class-level constraints however are called simultaneously with any other constraint annotations - no matter if they passed or failed. To (also) define an order between the constraints you can now use [[constraint groups|JavaForms#defining-the-order-of-constraint-groups]]. + +## JPA Migration Notes + +See [[JPA migration notes|JPAMigration26]]. + +## I18n Migration Notes + +See [[I18N API Migration|MessagesMigration26]]. + +## Cache APIs Migration Notes + +See [[Cache APIs Migration|CacheMigration26]]. + +## Java Configuration API Migration Notes + +See [[Java Configuration Migration|JavaConfigMigration26]]. + +## Scala Configuration API + +The Scala [`play.api.Configuration`](api/scala/play/api/Configuration.html) API now has new methods that allow loading any type using a [`ConfigLoader`](api/scala/play/api/ConfigLoader.html). These new methods expect configuration keys to exist in the configuration file. For example, the following old code: + +```scala +val myConfig: String = configuration.getString("my.config.key").getOrElse("default") +``` +should be changed to +```scala +val myConfig: String = configuration.get[String]("my.config.key") +``` +and the value "default" should be set in configuration as `my.config.key = default`. + +Alternatively, if custom logic is required in the code to obtain the default value, you can set the default to null in your config file (`my.config.key = null`), and read an `Option[T]`: +```scala +val myConfigOption: Option[String] = configuration.get[Option[String]]("my.config.key") +val myConfig: String = myConfigOption.getOrElse(computeDefaultValue()) +``` + +Also, there are several methods in the old [`play.api.Configuration`](api/scala/play/api/Configuration.html) that return Java types, like `getBooleanList`. We recommend using the Scala version `get[Seq[Boolean]]` instead if possible. If that is not possible, you can access the `underlying` Config object and call `getBooleanList` from it. + +The deprecation messages on the existing methods also explain how to migrate each method. See [[the Scala Configuration docs|ScalaConfig]] for more details on the proper use of [`play.api.Configuration`](api/scala/play/api/Configuration.html). + +## Play JSON API changes + +### JSON array index lookup + +If you are using the Scala play-json API, there was a small change in the way the `JsLookup` implicit class works. For example, if you have code like: + +```scala +val bar = (jsarray(index) \ "bar").as[Bar] +``` +where `index` is an array index and `jsarray` is a `JsArray`, now you should write: +```scala +val bar = (jsarray \ index \ "bar").as[Bar] +``` + +This was done to bring the behavior of indexing on `JsArray`s in line with that of other collections in Scala. Now the `jsarray(index)` method will return the value at the index, throwing an exception if it does not exist. + +## Removed APIs + +### Removed Crypto API + +The Crypto API has removed the deprecated classes `play.api.libs.Crypto`, `play.libs.Crypto` and `AESCTRCrypter`. The CSRF references to `Crypto` have been replaced by `CSRFTokenSigner`. The session cookie references to `Crypto` have been replaced with `CookieSigner`. Please see [[CryptoMigration25]] for more information. + +### `Akka` deprecated methods removed + +The deprecated static methods `play.libs.Akka.system` and `play.api.libs.concurrent.Akka.system` were removed. Use dependency injection to get an instance of `ActorSystem` and access to the actor system. + +For Scala: + +```scala +class MyComponent @Inject() (system: ActorSystem) { + +} +``` + +And for Java: + +```java +public class MyComponent { + + private final ActorSystem system; + + @Inject + public MyComponent(ActorSystem system) { + this.system = system; + } +} +``` + +Also, Play 2.6.x now uses the Akka 2.5.x release series. Read Akka [migration guide from 2.4.x to 2.5.x](https://doc.akka.io/docs/akka/current/project/migration-guide-2.4.x-2.5.x.html?language=scala) to see how to adapt your own code if necessary. + +### Removed Yaml API + +We removed `play.libs.Yaml` since there was no use of it inside of play anymore. If you still need support for the Play YAML integration you need to add `snakeyaml` in you `build.sbt`: + +```scala +libraryDependencies += "org.yaml" % "snakeyaml" % "1.17" +``` + +And create the following Wrapper in your Code: + +```java +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); + } + +} +``` + +Or in Scala: + +```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) + } +} +``` + +If you explicitly depend on an alternate DI library for Play, or have defined your own custom application loader, no changes should be required. + +Libraries that provide Play DI support should define the `play.application.loader` configuration key. If no external DI library is provided, Play will refuse to start unless you point that to an [`ApplicationLoader`](api/scala/play/api/ApplicationLoader.html). + +### Removed deprecated `play.Routes` + +The deprecated `play.Routes` class used to create a JavaScript router were removed. You now have to use the new Java or Scala helpers: + +* [[Javascript Routing in Scala|ScalaJavascriptRouting]] +* [[Javascript Routing in Java|JavaJavascriptRouter]] + +## Removed libraries + +In order to make the default play distribution a bit smaller we removed some libraries. The following libraries are no longer dependencies in Play 2.6, so you will need to manually add them to your build if you use them. + +### Joda-Time removal + +We recommend using the `java.time` APIs, so we are removing joda-time support from the core of Play. + +Play's Scala forms library had some Joda formats. If you don't wish to migrate, you can add the `jodaForms` module in your `build.sbt`: + +```scala +libraryDependencies += jodaForms +``` + +And then import the corresponding object: + +```scala +import play.api.data.JodaForms._ +``` + +If you need Joda support in play-json, you can add the following dependency: + +```scala +libraryDependencies += "com.typesafe.play" %% "play-json-joda" % playJsonVersion +``` + +where `playJsonVersion` is the play-json version you wish to use. Play 2.6.x should be compatible with play-json 2.6.x. Note that play-json is now a separate project (described later). + +```scala +import play.api.libs.json.JodaWrites._ +import play.api.libs.json.JodaReads._ +``` + +### Joda-Convert removal + +Play had some internal uses of `joda-convert` if you used it in your project you need to add it to your `build.sbt`: + +```scala +libraryDependencies += "org.joda" % "joda-convert" % "1.8.1" +``` + +### XercesImpl removal + +For XML handling Play used the Xerces XML Library. Since modern JVM are using Xerces as a reference implementation we removed it. If your project relies on the external package you can simply add it to your `build.sbt`: + +```scala +libraryDependencies += "xerces" % "xercesImpl" % "2.11.0" +``` + +### H2 removal + +Prior versions of Play prepackaged the H2 database. But to make the core of Play smaller we removed it. If you make use of H2 you can add it to your `build.sbt`: + +```scala +libraryDependencies += "com.h2database" % "h2" % "1.4.193" +``` + +If you only used it in your test you can also just use the `Test` scope: + +```scala +libraryDependencies += "com.h2database" % "h2" % "1.4.193" % Test +``` + +The [[H2 Browser|Developing-with-the-H2-Database#H2-Browser]] will still work after you added the dependency. + +### snakeyaml removal + +Play removed `play.libs.Yaml` and therefore the dependency on `snakeyaml` was dropped. If you still use it add it to your `build.sbt`: + +```scala +libraryDependencies += "org.yaml" % "snakeyaml" % "1.17" +``` + +See also [notes about the removal of Yaml API](#Removed-Yaml-API). + +### Tomcat-servlet-api removal + +Play removed the `tomcat-servlet-api` since it was of no use. If you still use it add it to your `build.sbt`: + +```scala +libraryDependencies += "org.apache.tomcat" % "tomcat-servlet-api" % "8.0.33" +``` + +### fork-run removal + +The `sbt-fork-run-plugin` will no longer be generated, as it was only needed for the now end-of-life activator utility. As it will no longer resolve for 2.6 it can safely be removed altogether. + +## Request attributes + +All request objects now contain *attributes*. Request attributes are a replacement for request *tags*. Tags have now been deprecated and you should upgrade to attributes. Attributes are more powerful than tags; you can use attributes to store objects in requests, whereas tags only supported storing Strings. + +### Request tags deprecation + +Tags have been deprecated so you should start migrating from using tags to using attributes. Migration should be fairly straightforward. + +The easiest migration path is to migrate from a tag to an attribute with a `String` type. + +Java before: + +```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 after: + +```java +class Attrs { + public static final TypedKey USER_NAME = TypedKey.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 before: + +```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 after: + +```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) +``` + +However, if appropriate, we recommend you convert your `String` tags into attributes with non-`String` values. Converting your tags into non-`String` objects has several benefits. First, you will make your code more type-safe. This will increase your code's reliability and make it easier to understand. Second, the objects you store in attributes can contain multiple properties, allowing you to aggregate multiple tags into a single value. Third, converting tags into attributes means you don't need to encode and decode values from `String`s, which may increase performance. + +```java +class Attrs { + public static final TypedKey USER = TypedKey.create("user"); +} +``` + +Scala after: + +```scala +object Attrs { + val UserName: TypedKey[User] = TypedKey("user") +} +``` + +### Calling `FakeRequest.withCookies` no longer updates the `Cookies` header + +Request cookies are now stored in a request attribute. Previously they were stored in the request's [`Cookie`](api/scala/play/api/mvc/Cookie.html) header `String`. This required encoding and decoding the cookie to the header whenever the cookie changed. + +Now that cookies are stored in request attributes updating the cookie will change the new cookie attribute but not the [`Cookie`](api/scala/play/api/mvc/Cookie.html) HTTP header. This will only affect your tests if you're relying on the fact that calling `withCookies` will update the header. + +If you still need the old behavior you can still use [`Cookies.encodeCookieHeader`](api/scala/play/api/mvc/Cookies$.html#encodeCookieHeader\(cookies:Seq[play.api.mvc.Cookie]\):String) to convert the [`Cookie`](api/scala/play/api/mvc/Cookie.html) objects into an HTTP header then store the header with `FakeRequest.withHeaders`. + +### `play.api.mvc.Security.username` (Scala API), `session.username` changes + +`play.api.mvc.Security.username` (Scala API), `session.username` config key and dependent actions helpers are deprecated. `Security.username` just retrieves the `session.username` key from configuration, which defined the session key used to get the username. It was removed since it required statics to work, and it's fairly easy to implement the same or similar behavior yourself. + +You can read the username session key from configuration yourself using `configuration.get[String]("session.username")`. + +If you're using the `Authenticated(String => EssentialAction)` method, you can easily create your own action to do something similar: + +```scala +def AuthenticatedWithUsername(action: String => EssentialAction) = + WithAuthentication[String](_.session.get(UsernameKey))(action) +``` + +where `UsernameKey` represents the session key you want to use for the username. + +### Request Security (Java API) username property is now an attribute + +The Java Request object contains a `username` property which is set when the `Security.Authenticated` annotation is added to a Java action. In Play 2.6 the username property has been deprecated. The username property methods have been updated to store the username in the `Security.USERNAME` attribute. You should update your code to use the `Security.USERNAME` attribute directly. In a future version of Play we will remove the username property. + +The reason for this change is that the username property was provided as a special case for the `Security.Authenticated` annotation. Now that we have attributes we don't need a special case anymore. + +Existing Java code: + +```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(); +``` + +Updated Java code: + +```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 are now attributes + +If you used any of the `Router.Tags.*` tags, you should change your code to use the new [`Router.Attrs.HandlerDef`](api/scala/play/api/routing/Router$$Attrs$.html#HandlerDef:play.api.libs.typedmap.TypedKey[play.api.routing.HandlerDef]) (Scala) or [`Router.Attrs.HANDLER_DEF`](api/java/play/routing/Router.Attrs.html#HANDLER_DEF) (Java) attribute instead. The existing tags are still available, but are deprecated and will be removed in a future version of Play. + +This new attribute contains a `HandlerDef` object with all the information that is currently in the tags. The current tags all correspond to a field in the `HandlerDef` object: + +| Java tag name | Scala tag name | `HandlerDef` method | +|:----------------------|:--------------------|:--------------------| +| `ROUTE_PATTERN` | `RoutePattern` | `path` | +| `ROUTE_VERB` | `RouteVerb` | `verb` | +| `ROUTE_CONTROLLER` | `RouteController` | `controller` | +| `ROUTE_ACTION_METHOD` | `RouteActionMethod` | `method` | +| `ROUTE_COMMENTS` | `RouteComments` | `comments` | + +> **Note**: As part of this change the `HandlerDef` object has been moved from the `play.core.routing` internal package into the `play.api.routing` public API package. + +## `play.api.libs.concurrent.Execution` is deprecated + +The `play.api.libs.concurrent.Execution` class has been deprecated, as it was using global mutable state under the hood to pull the "current" application's ExecutionContext. + +If you want to specify the implicit behavior that you had previously, then you should pass in the execution context implicitly in the constructor using [[dependency injection|ScalaDependencyInjection]]: + +```scala +class MyController @Inject()(implicit ec: ExecutionContext) { + +} +``` + +or from BuiltInComponents if you are using [[compile time dependency injection|ScalaCompileTimeDependencyInjection]]: + +```scala +class MyComponentsFromContext(context: ApplicationLoader.Context) + extends BuiltInComponentsFromContext(context) { + val myComponent: MyComponent = new MyComponent(executionContext) +} +``` + +However, there are some good reasons why you may not want to import an execution context even in the general case. In the general case, the application's execution context is good for rendering actions, and executing CPU-bound activities that do not involve blocking API calls or I/O activity. If you are calling out to a database, or making network calls, then you may want to define your own custom execution context. + +The recommended way to create a custom execution context is through [`CustomExecutionContext`](api/scala/play/api/libs/concurrent/CustomExecutionContext.html), which uses the Akka dispatcher system ([java](https://doc.akka.io/docs/akka/2.5/dispatchers.html?language=java) / [scala](https://doc.akka.io/docs/akka/2.5/dispatchers.html?language=scala)) so that executors can be defined through configuration. + +To use your own execution context, extend the [`CustomExecutionContext`](api/scala/play/api/libs/concurrent/CustomExecutionContext.html) abstract class with the full path to the dispatcher in the `application.conf` file: + +```scala +import play.api.libs.concurrent.CustomExecutionContext + +class MyExecutionContext @Inject()(actorSystem: ActorSystem) + extends CustomExecutionContext(actorSystem, "my.dispatcher.name") +``` + +```java +import play.libs.concurrent.CustomExecutionContext; +class MyExecutionContext extends CustomExecutionContext { + @Inject + public MyExecutionContext(ActorSystem actorSystem) { + super(actorSystem, "my.dispatcher.name"); + } +} +``` + +and then inject your custom execution context as appropriate: + +```scala +class MyBlockingRepository @Inject()(implicit myExecutionContext: MyExecutionContext) { + // do things with custom execution context +} +``` + +Please see [[ThreadPools]] page for more information on custom thread pool configuration, and [[JavaAsync]] / [[ScalaAsync]] for using `CustomExecutionContext`. + +## Changes to play.api.test Helpers + +The following deprecated test helpers have been removed in 2.6.x: + +* `play.api.test.FakeApplication` has been replaced by [`play.api.inject.guice.GuiceApplicationBuilder`](api/scala/play/api/inject/guice/GuiceApplicationBuilder.html). +* The `play.api.test.Helpers.route(request)` has been replaced with the `play.api.test.Helpers.routes(app, request)` method. +* The `play.api.test.Helpers.route(request, body)` has been replaced with the [`play.api.test.Helpers.routes(app, request, body)`](api/scala/play/api/test/Helpers$.html) method. + +### Java API + +* `play.test.FakeRequest` has been replaced by [`RequestBuilder`](api/java/play/mvc/Http.RequestBuilder.html) +* `play.test.FakeApplication` has been replaced with `play.inject.guice.GuiceApplicationBuilder`. You can create a new `Application` from [`play.test.Helpers.fakeApplication`](api/java/play/inject/guice/GuiceApplicationBuilder.html). +* In `play.test.WithApplication`, the deprecated `provideFakeApplication` method has been removed -- the `provideApplication` method should be used. + + +## Changes to Template Helpers + +The `requireJs` template helper in [`views/helper/requireJs.scala.html`](https://github.com/playframework/playframework/blob/master/framework/src/play/src/main/scala/views/helper/requireJs.scala.html) used `Play.maybeApplication` to access the configuration. + +The `requireJs` template helper has an extra parameter `isProd` added to it that indicates whether the minified version of the helper should be used: + +``` +@requireJs(core = routes.Assets.at("javascripts/require.js").url, module = routes.Assets.at("javascripts/main").url, isProd = true) +``` + +## Changes to File Extension to MIME Type Mapping + +The mapping of file extensions to MIME types has been moved to `reference.conf` so it is covered entirely through configuration, under `play.http.fileMimeTypes` setting. Previously the list was hardcoded under `play.api.libs.MimeTypes`. + +Note that `play.http.fileMimeTypes` configuration setting is defined using triple quotes as a single string -- this is because several file extensions have syntax that breaks HOCON, such as `c++`. + +To append a custom MIME type, use [HOCON string value concatenation](https://github.com/typesafehub/config/blob/master/HOCON.md#string-value-concatenation): + +``` +play.http.fileMimeTypes = ${play.http.fileMimeTypes} """ + foo=text/bar +""" +``` + +There is a syntax that allows configurations defined as `mimetype.foo=text/bar` for additional MIME types. This is deprecated, and you are encouraged to use the above configuration. + +### Java API + +There is a `Http.Context.current().fileMimeTypes()` method that is provided under the hood to `Results.sendFile` and other methods that look up content types from file extensions. No migration is necessary. + +### Scala API + +The `play.api.libs.MimeTypes` class has been changed to [`play.api.http.FileMimeTypes`](api/scala/play/api/http/FileMimeTypes.html) interface, and the implementation has changed to [`play.api.http.DefaultFileMimeTypes`](api/scala/play/api/http/DefaultFileMimeTypes.html). + +All the results that send files or resources now take `FileMimeTypes` implicitly, i.e. + +```scala +implicit val fileMimeTypes: FileMimeTypes = ... +Ok(file) // <-- takes implicit FileMimeTypes +``` + +An implicit instance of `FileMimeTypes` is provided by `BaseController` (and its subclass `AbstractController` and subtrait `InjectedController`) through the `ControllerComponents` class, to provide a convenient binding: + +```scala +class SendFileController @Inject() (cc: ControllerComponents) extends AbstractController(cc) { + + def index() = Action { implicit request => + val file = readFile() + Ok(file) // <-- takes implicit FileMimeTypes + } +} +``` + +You can also get a fully configured `FileMimeTypes` instance directly in a unit test: + +```scala +val httpConfiguration = new HttpConfigurationProvider(Configuration.load(Environment.simple)).get +val fileMimeTypes = new DefaultFileMimeTypesProvider(httpConfiguration.fileMimeTypes).get +``` + +Or get a custom one: + +```scala +val fileMimeTypes = new DefaultFileMimeTypesProvider(FileMimeTypesConfiguration(Map("foo" -> "text/bar"))).get +``` + +## Default Filters + +Play now comes with a default set of enabled filters, defined through configuration. If the property `play.http.filters` is null, then the default is now [`play.api.http.EnabledFilters`](api/scala/play/api/http/EnabledFilters.html), which loads up the filters defined by fully qualified class name in the `play.filters.enabled` configuration property. + +In Play itself, `play.filters.enabled` is an empty list. However, the filters library is automatically loaded in sbt as an AutoPlugin called `PlayFilters`, and will append the following values to the `play.filters.enabled` property: + +* [`play.filters.csrf.CSRFFilter`](api/scala/play/filters/csrf/CSRFFilter.html) +* [`play.filters.headers.SecurityHeadersFilter`](api/scala/play/filters/headers/SecurityHeadersFilter.html) +* [`play.filters.hosts.AllowedHostsFilter`](api/scala/play/filters/hosts/AllowedHostsFilter.html) + +This means that on new projects, CSRF protection ([[ScalaCsrf]] / [[JavaCsrf]]), [[SecurityHeaders]] and [[AllowedHostsFilter]] are all defined automatically. + +### Effects of Default Filters + +The default filters are configured to give a "secure by default" configuration to projects. + +**You should keep these filters enabled: they make your application more secure.** + +If you did not have these filters enabled in an existing project, then there is some configuration required, and you may not be familiar with the errors and failures involved. To help with migration, we'll go over each filter, what it does and what configuration is required. + +#### CSRFFilter + +The CSRF filter is described in [[ScalaCsrf]] and [[JavaCsrf]]. It protects against [cross site request forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) attacks, by adding a CSRF token to forms that is checked on POST requests. + +##### Why it is enabled by default + +CSRF is a very common attack that takes very little skill to implement. You can see an example of a CSRF attack using Play at [https://github.com/Manc/play-scala-csrf](https://github.com/Manc/play-scala-csrf). + +##### What changes do I need to make? + +If you are migrating from an existing project that does not use CSRF form helpers such as `CSRF.formField`, then you may see "403 Forbidden" on PUT and POST requests from the CSRF filter. + +Adding `CSRF.formField` to your form templates will resolve the error If you are making requests with AJAX, you can place the CSRF token in the HTML page, and then add it to the request using the `Csrf-Token` header. + +To check this behavior, please add `` to your `logback.xml`. + +You may also want to enable SameSite cookies in Play, which provide an additional defense against CSRF attacks. + +#### SecurityHeadersFilter + +[[SecurityHeadersFilter|SecurityHeaders]] prevents [cross site scripting](https://en.wikipedia.org/wiki/Cross-site_scripting) and [clickjacking](https://en.wikipedia.org/wiki/Clickjacking) attacks, by adding extra HTTP headers to the request. + +##### Why it is enabled by default + +Browser based attacks are extremely common, and security headers can provide a defense in depth to help frustrate those attacks. + +##### What changes do I need to make? + +The default "Content-Security-Policy" settings are quite strict, and it is likely that you will need to experiment with it to find the most useful settings. The Content-Security-Policy settings will change how Javascript and remote frames are displayed in a browser. **Embedded Javascript or CSS will not be loaded in your web page until you modify the Content-Security-Policy header.** + +If you are sure that you do not want to enable it, you can disable the Content-Security-Policy as follows: + +``` +play.filters.headers.contentSecurityPolicy=null +``` + +[CSP-Useful](https://github.com/nico3333fr/CSP-useful) is a good resource on Content-Security-Policy in general. Note that there are other potential solutions to embedded Javascript, such as adding a custom CSP nonce on every request. + +The other headers are less intrusive, and are unlikely to cause problems on a plain website, but may cause cookie or rendering problems on a Single Page Application. Mozilla has documentation describing each header in detail, using the header name in the URL: for example, for X-Frame-Options go to [https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/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 + +The AllowedHostsFilter adds a whitelist of allowed hosts and sends a 400 (Bad Request) response to all requests with a host that do not match the whitelist. + +##### Why it is enabled by default + +This is an important filter to use in development, because DNS rebinding attacks can be used against a developer’s instance of Play: see [Rails Webconsole DNS Rebinding](https://benmmurphy.github.io/blog/2016/07/11/rails-webconsole-dns-rebinding/) for an example of how short lived DNS rebinding can attack a server running on localhost. + +##### What changes do I need to make? + +If you are running a Play application on something other than localhost, you must configure the AllowedHostsFilter to specifically allow the hostname/ip you are connecting from. This is especially important to note when you change environments, because typically you'll run on localhost in development, but will run remotely in staging and production. + +``` +play.filters.hosts { + # Allow requests to example.com, its subdomains, and localhost:9000. + allowed = [".example.com", "localhost:9000"] +} +``` + +### Appending To Filters + +To append to the defaults list, use the `+=`: + +``` +play.filters.enabled+=MyFilter +``` + +If you have defined your own filters by extending `play.api.http.DefaultHttpFilters`, then you can also combine `EnabledFilters` with your own list in code, so if you have previously defined projects, they still work as usual: + +```scala +class Filters @Inject()(enabledFilters: EnabledFilters, corsFilter: CORSFilter) + extends DefaultHttpFilters(enabledFilters.filters :+ corsFilter: _*) +``` + +### Testing Default Filters + +Because there are several filters enabled, functional tests may need to change slightly to ensure that all the tests pass and requests are valid. For example, a request that does not have a `Host` HTTP header set to `localhost` will not pass the AllowedHostsFilter and will return a 400 Forbidden response instead. + +#### Testing with AllowedHostsFilter + +Because the AllowedHostsFilter filter is added automatically, functional tests need to have the Host HTTP header added. + +If you are using `FakeRequest` or `Helpers.fakeRequest`, then the `Host` HTTP header is added for you automatically. If you are using `play.mvc.Http.RequestBuilder`, then you may need to add your own line to add the header manually: + +```java +RequestBuilder request = new RequestBuilder() + .method(GET) + .header(HeaderNames.HOST, "localhost") + .uri("/xx/Kiwi"); +``` + +#### Testing with CSRFFilter + +Because the CSRFFilter filter is added automatically, tests that render a Twirl template that includes `CSRF.formField`, i.e. + +```scala +@(userForm: Form[UserData])(implicit request: RequestHeader, m: Messages) + +

user form

+ +@request.flash.get("success").getOrElse("") + +@helper.form(action = routes.UserController.userPost()) { + @helper.CSRF.formField + @helper.inputText(userForm("name")) + @helper.inputText(userForm("age")) + +} +``` + +must contain a CSRF token in the request. In the Scala API, this is done by importing `play.api.test.CSRFTokenHelper._`, which enriches `play.api.test.FakeRequest` with the `withCSRFToken` method: + +```scala +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") + } + } +} +``` + +In the Java API, this is done by calling `CSRFTokenHelper.addCSRFToken` on a `play.mvc.Http.RequestBuilder` instance: + +``` +requestBuilder = CSRFTokenHelper.addCSRFToken(requestBuilder); +``` + +### Disabling Default Filters + +The simplest way to disable the default filters is to set the list of filters manually in `application.conf`: + +``` +play.filters.enabled=[] +``` + +This may be useful if you have functional tests that you do not want to go through the default filters. + +If you want to remove all filter classes, you can disable it through the `disablePlugins` mechanism: + +``` +lazy val root = project.in(file(".")).enablePlugins(PlayScala).disablePlugins(PlayFilters) +``` + +or by replacing `EnabledFilters`: + +``` +play.http.filters=play.api.http.NoHttpFilters +``` + +If you are writing functional tests involving `GuiceApplicationBuilder` and you want to disable default filters, then you can disable all or some of the filters through configuration by using `configure`: + +```scala +GuiceApplicationBuilder().configure("play.http.filters" -> "play.api.http.NoHttpFilters") +``` + +## Compile Time Default Filters + +If you are using compile time dependency injection, then the default filters are resolved at compile time, rather than through runtime. + +This means that the `BuiltInComponents` trait now contains an `httpFilters` method which is left abstract: + +```scala +trait BuiltInComponents { + + /** A user defined list of filters that is appended to the default filters */ + def httpFilters: Seq[EssentialFilter] +} +``` + +The default list of filters is defined in `play.filters.HttpFiltersComponents`: + +```scala +trait HttpFiltersComponents + extends CSRFComponents + with SecurityHeadersComponents + with AllowedHostsComponents { + + def httpFilters: Seq[EssentialFilter] = Seq(csrfFilter, securityHeadersFilter, allowedHostsFilter) +} +``` + +In most cases you will want to mixin HttpFiltersComponents and append your own filters: + +```scala +class MyComponents(context: ApplicationLoader.Context) + extends BuiltInComponentsFromContext(context) + with play.filters.HttpFiltersComponents { + + lazy val loggingFilter = new LoggingFilter() + override def httpFilters = { + super.httpFilters :+ loggingFilter + } +} +``` + +If you want to filter elements out of the list, you can do the following: + +```scala +class MyComponents(context: ApplicationLoader.Context) + extends BuiltInComponentsFromContext(context) + with play.filters.HttpFiltersComponents { + override def httpFilters = { + super.httpFilters.filterNot(_.getClass == classOf[CSRFFilter]) + } +} +``` + +### Disabling Compile Time Default Filters + +To disable the default filters, mixin `play.api.NoHttpFiltersComponents`: + +```scala +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 Support + +Play's cookie encoding has been switched to use JSON Web Token (JWT) under the hood. JWT comes with a number of advantages, notably automatic signing with HMAC-SHA-256, and support for automatic "not before" and "expires after" date checks which ensure the session cookie cannot be reused outside of a given time window. + +More information is available under [[Configuring the Session Cookie|SettingsSession]] page. + +### Fallback Cookie Support + +Play's cookie encoding uses a "fallback" cookie encoding mechanism that reads in JWT encoded cookies, then attempts reading a URL encoded cookie if the JWT parsing fails, so you can safely migrate existing session cookies to JWT. This functionality is in the `FallbackCookieDataCodec` trait and leveraged by `DefaultSessionCookieBaker` and `DefaultFlashCookieBaker`. + +### Legacy Support + +Using JWT encoded cookies should be seamless, but if you want, you can revert back to URL encoded cookie encoding by switching to `play.api.mvc.LegacyCookiesModule` in application.conf file: + +``` +play.modules.disabled+="play.api.mvc.CookiesModule" +play.modules.enabled+="play.api.mvc.LegacyCookiesModule" +``` + +### Custom CookieBakers + +If you have custom cookies being used in Play, using the `CookieBaker[T]` trait, then you will need to specify what kind of encoding you want for your custom cookie baker. + +The `encode` and `decode` methods that `Map[String, String]` to and from the format found in the browser have been extracted into `CookieDataCodec`. There are three implementations: `FallbackCookieDataCodec`, `JWTCookieDataCodec`, or `UrlEncodedCookieDataCodec`, which respectively represent URL-encoded with an HMAC, or a JWT, or a "read signed or JWT, write JWT" codec. + +You will also need to provide a `JWTConfiguration` case class, using the `JWTConfigurationParser` with the path to your configuration, or use `JWTConfiguration()` for the defaults. + + +```scala +@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() +} +``` + +## Deprecated Futures methods + +The following `play.libs.concurrent.Futures` static methods have been deprecated: + +* `timeout(A value, long amount, TimeUnit unit)` +* `timeout(final long delay, final TimeUnit unit)` +* `delayed(Supplier
supplier, long delay, TimeUnit unit, Executor executor)` + +A dependency injected instance of `Futures` should be used instead: + +```java +class MyClass { + @Inject + public MyClass(play.libs.concurrent.Futures futures) { + this.futures = futures; + } + + CompletionStage callWithOneSecondTimeout() { + return futures.timeout(computePIAsynchronously(), Duration.ofSeconds(1)); + } +} +``` + +## Updated libraries + +### Netty 4.1 + +Netty was upgraded to [version 4.1](https://netty.io/news/2016/05/26/4-1-0-Final.html). This was possible mainly because version 4.0 was shaded by [[play-ws migration to a standalone module|WSMigration26]]. So, if you are using [[Netty Server|NettyServer]] and some library that depends on Netty 4.0, we recommend that you try to upgrade to a newer version of the library, or you can start to use the [[Akka Server|AkkaHttpServer]]. + +And if you are, for some reason, directly using Netty classes, you should [adapt your code to this new version](https://netty.io/wiki/new-and-noteworthy-in-4.1.html). + +### FluentLenium and Selenium + +The FluentLenium library was updated to version 3.2.0 and Selenium was updated to version [3.3.1](https://seleniumhq.wordpress.com/2016/10/13/selenium-3-0-out-now/) (you may want to see the [changelog here](https://raw.githubusercontent.com/SeleniumHQ/selenium/master/java/CHANGELOG)). If you were using Selenium's WebDriver API before, there should not be anything to do. Please check [this](https://seleniumhq.wordpress.com/2016/10/04/selenium-3-is-coming/) announcement for further information. +If you were using the FluentLenium library you might have to change some syntax to get your tests working again. Please see FluentLenium's [Migration Guide](http://fluentlenium.org/migration/from-0.13.2-to-1.0-or-3.0/) for more details about how to adapt your code. + +### HikariCP + +HikariCP was updated and a new configuration was introduced: `initializationFailTimeout`. This new configuration should be used to replace `initializationFailFast` which is now deprecated. See [HikariCP changelog](https://github.com/brettwooldridge/HikariCP/blob/dev/CHANGES) and [documentation for `initializationFailTimeout`](https://github.com/brettwooldridge/HikariCP#infrequently-used) to better understand how to use this new configuration. + +## Other Configuration changes + +There are some configuration changes. The old configuration paths will generally still work, but a deprecation warning will be output at runtime if you use them. Here is a summary of the changed keys: + +| Old key | New key | +|-------------------------------|-----------------------------------------| +| `play.crypto.secret` | `play.http.secret.key` | +| `play.crypto.provider` | `play.http.secret.provider` | +| `play.websocket.buffer.limit` | `play.server.websocket.frame.maxLength` | diff --git a/manual/releases/release26/migration26/WSMigration26.md b/manual/releases/release26/migration26/WSMigration26.md new file mode 100644 index 00000000..049b165f --- /dev/null +++ b/manual/releases/release26/migration26/WSMigration26.md @@ -0,0 +1,117 @@ + +# Play WS Migration Guide + +Play WS now has a standalone version - [https://github.com/playframework/play-ws](https://github.com/playframework/play-ws) - that can be used outside a Play project. If you have a Play sbt project, you can still add WS by adding the following line to your `build.sbt`: + +```scala +libraryDependencies += ws +``` + +This includes the `play-ahc-ws` module, which wraps the standalone version with Play Dependency Injection bindings and components, configuration and anything else that is necessary to better integrate it. + + +And if you want to use the cache support, you need to add `ws`, `ehcache` and [[enable and configure cache|WsCache]]: + +```scala +libraryDependencies += ws +libraryDependencies += ehcache +``` + +If you want to use it in a non Play project, it can be added to an sbt project with: + +```scala +libraryDependencies += "com.typesafe.play" %% "play-ahc-ws-standalone" % "1.1.2" +libraryDependencies += "com.typesafe.play" %% "play-ws-standalone-json" % "1.1.2" +libraryDependencies += "com.typesafe.play" %% "play-ws-standalone-xml" % "1.1.2" +``` + +## Package changes + +Play WS historically consisted of two libraries, `ws` and `playWs`, containing the Scala and Java APIs respectively, each individually creating an AsyncHTTPClient behind the scenes. There is now only one `play-ahc-ws` library, which contains both Scala and Java `WSClient` instances, and both point to a singleton `AsyncHttpClient` provider. + +## Project changes + +Play WS now exists as a Play specific wrapper on top of a standalone WS library, which does not depend on Play classes, and which uses package renamed "shaded" versions of AsyncHttpClient, Signpost, and Netty 4.0. + +By providing a standalone WS version and using shaded libraries, WS is more flexible and has fewer collisions with other libraries and projects. + +The Play WS API extends Standalone WS `post` with `Http.Multipart` and `Multipart` types that are only available in Play, for example: + +```scala +def withBody(body: Source[MultipartFormData.Part[Source[ByteString, _]], _]): Self +``` + +Signpost OAuth has been changed so that instead of using the Commons HTTPClient OAuthProvider, it now uses the DefaultOAuthProvider, which uses HTTPURLConnection under the hood. + +## API changes + +### Scala + +The `WSAPI` class has been removed. The `WSClient` interface is the point of entry for the WS API. + +`WSRequest` had a `withBody[T](body: T)(implicit writable: play.api.http.Writable[T])` method has been replaced as it was difficult to track the behavior of `Writable`. There is now a custom `BodyWritable[T]` type class that fills the same function, and which has type class instances defined in Standalone WS: + +```scala +override def withBody[T: BodyWritable](body: T) +``` + +The deprecated Scala singleton object `play.api.libs.ws.WS` has been removed. An instance of `WSClient` should be used instead. If compile time dependency injection is being used, then the `AhcWSComponents` trait should be mixed in. + +For Guice, there is a `WSClient` available in the system: + +```scala +class MyService @Inject()(ws: WSClient) { + def call(): Unit = { + ws.url("http://localhost:9000/foo").get() + } +} +``` + +If you cannot use an injected WSClient instance, then you can also create your [[own instance of WSClient|ScalaWS#using-wsclient]], but you are then responsible for managing the lifecycle of the client. + +If you are running a functional test, you can use the `play.api.test.WsTestClient`, which will start up and shut down a standalone WSClient instance: + +```scala +play.api.test.WsTestClient.withClient { ws => + ws.url("http://localhost:9000/foo").get() +} +``` + +The `ning` package has been replaced by the `ahc` package, and the Ning* classes replaced by AHC*. + +A normal `WSResponse` instance is returned from `stream()` instead of `StreamedResponse`. You should call `response.bodyAsSource` to return the streamed result. + +There are some naming changes with deprecations to make things more explicit on `play.api.libs.ws.WSRequest`. Extra care should be exercised when migrating these: +- [`WsRequest.withHeaders`](https://playframework.com/documentation/2.6.x/api/scala/index.html#play.api.libs.ws.WSRequest@withHeaders\(headers:\(String,String\)*\):WSRequest.this.Self) is now [`WsRequest.addHttpHeaders`](https://playframework.com/documentation/2.6.x/api/scala/index.html#play.api.libs.ws.WSRequest@addHttpHeaders\(hdrs:\(String,String\)*\):StandaloneWSRequest.this.Self) (same behaviour) or [`WsRequest.withHttpHeaders`](https://playframework.com/documentation/2.6.x/api/scala/index.html#play.api.libs.ws.WSRequest@withHttpHeaders\(headers:\(String,String\)*\):WSRequest.this.Self) (throws away existing headers) +- [`WsRequest.withQueryString`](https://playframework.com/documentation/2.6.x/api/scala/index.html#play.api.libs.ws.WSRequest@withQueryString\(parameters:\(String,String\)*\):WSRequest.this.Self) is now [`WsRequest.addQueryStringParameters`](https://playframework.com/documentation/2.6.x/api/scala/index.html#play.api.libs.ws.WSRequest@addQueryStringParameters\(parameters:\(String,String\)*\):StandaloneWSRequest.this.Self) (same behaviour) or [`WsRequest.withQueryStringParameters`](https://playframework.com/documentation/2.6.x/api/scala/index.html#play.api.libs.ws.WSRequest@withQueryStringParameters\(parameters:\(String,String\)*\):WSRequest.this.Self) (throws away existing query string) + +### Java + +In Java, the `play.libs.ws.WS` class has been deprecated. An injected `WSClient` instance should be used instead. + +```java +public class MyService { + private final WSClient ws; + + @Inject + public MyService(WSClient ws) { + this.ws = ws; + } + + public void call() { + ws.url("http://localhost:9000/foo").get(); + } +} +``` + +If you cannot use an injected WSClient instance, then you can also create your [[own instance of WSClient|JavaWS#using-wsclient]], but you are then responsible for managing the lifecycle of the client. + +If you are running a functional test, you can use the `play.test.WsTestClient`, which will start up and shut down a standalone `WSClient` instance: + +```java +WSClient ws = play.test.WsTestClient.newClient(19001); +... +ws.close(); +``` + +A normal `WSResponse` instance is returned from `stream()` instead of `StreamedResponse`. You should call `response.getBodyAsSource()` to return the streamed result. diff --git a/manual/releases/release26/migration26/index.toc b/manual/releases/release26/migration26/index.toc new file mode 100644 index 00000000..136cc2e7 --- /dev/null +++ b/manual/releases/release26/migration26/index.toc @@ -0,0 +1,6 @@ +Migration26:Migration Guide +MessagesMigration26:Messages Migration +WSMigration26:WS Migration +CacheMigration26:Cache Migration +JPAMigration26:JPA Migration +JavaConfigMigration26: Java Configuration API Migration \ No newline at end of file diff --git a/manual/tutorial/HelloWorldTutorial.md b/manual/tutorial/HelloWorldTutorial.md new file mode 100644 index 00000000..e90908f2 --- /dev/null +++ b/manual/tutorial/HelloWorldTutorial.md @@ -0,0 +1,41 @@ + + +# Hello World Tutorial + +This tutorial describes how Play applications work, and shows you how to create a page that displays a customized Hello World greeting. + +You can use any Java build tool to build a Play project. This tutorial demonstrates sbt and Gradle because they both provide the development experience Play is known and loved for, such as auto-reloading, clear error messages, and template compilation. The tutorial procedures assume use of `sbt` or `gradlew` commands from a terminal, but you can also integrate Play projects with your favorite [[IDE]]. + +## Starting the project + +Before following the tutorial instructions: + +1. Make sure you have verified the [[requirements for running Play|Requirements]] +1. Obtain the appropriate example zip file: + 1. [Play Java Starter Example](https://developer.lightbend.com/start/?group=play&project=play-java-starter-example) + 1. [Play Scala Starter Example](https://developer.lightbend.com/start/?group=play&project=play-scala-starter-example) +1. Unzip and run the example following the steps in the `README.md` file. + + +> **Note**: When you run the tutorial application, it displays web pages with the same content and instructions contained here in the documentation. The tutorial includes a deliberate mistake and having the documenation and application pages open in different tabs or browsers allows you to consult the documentation for the fix when you encounter the error. + +## Introduction to Play + +As illustrated below, Play is a full-stack framework with all of the components you need to build a Web Application or a REST service, including: an integrated HTTP server, form handling, Cross-Site Request Forgery (CSRF) protection, a powerful routing mechanism, I18n support, and more. Play integrates with many object relational mapping (ORM) layers. It supports [[Anorm]], [[Ebean|JavaEbean]], [[Slick|PlaySlick]], and [[JPA|JavaJPA]] out-of-the-box, but many customers use NoSQL, other ORMs or even access data from a REST service. + +[[images/play-stack.png]] + +Play APIs are available in both Java and Scala. The Framework uses [Akka](https://akka.io) and [Akka HTTP](https://doc.akka.io/docs/akka-http/current/index.html) under the hood. This endows Play applications with a stateless, non-blocking, event-driven architecture that provides horizontal and vertical scalability and uses resources more efficiently. Play projects contain Scala components, but because Play has a Java API, Java developers do not need to learn Scala to use Play successfully. + +Here are just a few of the reasons developers love using Play Framework: + +- Its Model-View-Controller (MVC) architecture is familiar and easy to learn. +- Direct support of common web development tasks and hot reloading saves precious development time. +- A large active community promotes knowledge sharing. +- [Twirl templates](https://github.com/playframework/twirl) render pages. The Twirl template language is: + - Easy to learn + - Requires no special editor + - Provides type safety + - Is compiled so that errors display in the browser + +To learn more about Play's benefits, see Play's [[Introduction]] and [[Philosophy]]. Now, let's dive into what a Play application looks like. diff --git a/manual/tutorial/ImplementingHelloWorld.md b/manual/tutorial/ImplementingHelloWorld.md new file mode 100644 index 00000000..370ba458 --- /dev/null +++ b/manual/tutorial/ImplementingHelloWorld.md @@ -0,0 +1,114 @@ + + +# Implementing Hello World + +To see how simple it is to work with Play, let's add a customized `"Hello World"` greeting to this tutorial app. + +The main steps include: + +1. Create the Hello World page +1. Add an action method +1. Define a route +1. Customize the greeting + +## 1. Create the Hello World page + +Follow the instructions below to add a new Hello World page to this project. + +With any text editor, create a file named `hello.scala.html` and save it in the `app/views` directory of this project. Add the following contents to the file: + +@[hello-world-page](code/javaguide/hello/hello.scala.html) + +This Twirl and HTML markup accomplishes the following: + +1. The `@` sign tells the template engine to interpret what follows. +1. In this case, `@main("Hello")` calls the main template, `main.scala.html` and passes it the page title of `"Hello"`. +1. The content section contains the `Hello World` greeting. The main template will insert this into the body of the page. + +Now we are ready to add an action method that will render the new page. + +## 2. Add an action method + +To add an action method for the new page: + +Open the `app/controllers/HomeController.java` (or `.scala`) file. Under the tutorial method and before the closing brace, add the following method: + +Java +: +@[hello-world-hello-action](code/javaguide/hello/HelloController.java) + +Scala +: +@[hello-world-hello-action](code/scalaguide/hello/HelloController.scala) + +To have Play call the new action method when the browser requests the `hello` page, we need to add a route that maps the page to the method. + +## 3. Define a route + +To define a route for the new Hello page: + +Open the `conf/routes` file and add the following line: + +@[hello-world-hello-route](code/routes) + +When you add a route to the `routes` file, Play's routes compiler will automatically generate a router class that calls that action using an instance of your controller. For more information see the [routing documentation](https://www.playframework.com/documentation/2.6.x/ScalaRouting#HTTP-routing). By default, the controller instances are created using dependency injection (see docs for [[Java|JavaDependencyInjection]] and [[Scala|ScalaDependencyInjection]]). + +You are now ready to test the new page. If you stopped the application for some reason, restart it with the `sbt run` command. + +Enter the URL to view the results of your work. The browser should respond with something like the following: + +[[images/hello-page.png]] + +## 4. Customize the greeting + +As the final part of this tutorial, we'll modify the hello page to accept an HTTP request parameter. The steps include a deliberate mistake to demonstrate how Play provides useful feedback. + +To customize the Hello World greeting, follow the instructions below. + +In the `app/controllers/HomeController.java` (or `.scala`) file, modify the `hello` action method to accept a name parameter using the following code: + +Java +: +@[hello-world-hello-error-action](code/javaguide/hello/HelloController.java) + +Scala +: +@[hello-world-hello-error-action](code/scalaguide/hello/HelloController.scala) + +In the `conf/routes` file, add a `(name: String)` parameter at the end of the `hello`: + +@[hello-world-hello-name-route](code/routes) + +In Twirl templates, all variables and their types must be declared. In the `app/views/hello.scala.html` file: + +1. Insert a new line at the top of the file. +1. On that line, add an @ directive that declares the name parameter and its type: `@(name: String)` +1. To use the variable on the page, change the text in the `

` heading from `Hello World!` to `

Hello @name!

`. + +The end result will be: + +@[](code/javaguide/hello/helloName.scala.html) + +In the browser, enter the following URL and pass in any name as a query parameter to the hello method: . Play responds with a helpful compilation error that lets you know that the render method in the return value requires a typed parameter: + +[[images/hello-error.png]] + +To fix the compilation error, modify the `hello` action method in `HomeController` so that the it includes the `name` parameter when rendering the view: + +Java +: +@[hello-world-hello-correct-action](code/javaguide/hello/HelloController.java) + +Scala +: +@[hello-world-hello-correct-action](code/scalaguide/hello/HelloController.scala) + +Save the file and refresh the browser. The page should display a customized greeting similar to the following: + +[[images/hello-name.png]] + +## Summary + +Thanks for trying our tutorial. You learned how to use an action method, routes, Twirl template, and input parameter to create a customized Hello World greeting! You experienced how template compilation makes it easier to identify and fix problems and how auto-reloading saves time. + +This was just a simple example to get you started. Let's now see other official examples and tutorials from the community. diff --git a/manual/tutorial/PlayApplicationOverview.md b/manual/tutorial/PlayApplicationOverview.md new file mode 100644 index 00000000..f3858831 --- /dev/null +++ b/manual/tutorial/PlayApplicationOverview.md @@ -0,0 +1,51 @@ + + +# Play Application Overview + +This tutorial is implemented as a simple Play application that we can examine to start learning about Play. Let's first look at what happens at runtime. When you enter in your browser: + +1. The browser requests the root `/` URI from the HTTP server using the `GET` method. +1. The Play internal HTTP Server receives the request. +1. Play resolves the request using the `routes` file, which maps URIs to controller action methods. +1. The action method renders the `index` page, using Twirl templates. +1. The HTTP server returns the response as an HTML page. + +At a high level, the flow looks something like this: + +[[images/play-request-response.png]] + +## Explore the project + +Next, let's look at the tutorial project to locate the implementation for: + +1. The routes file that maps the request to the controller method. +1. The controller action method that defines how to handle a request to the root URI. +1. The Twirl template that the action method calls to render the HTML markup. + +Follow these steps to drill down into the source files: + +> **Note:** In the following procedures, for Windows shells, use \ in place of / in path names (no need to change URL path names though). + +Using a command window or GUI, look at the contents of the top-level project directory. The following directories contain application components: + +1. The `app` subdirectory contains directories for `controllers` and `views`, which will be familiar to those experienced with the Model View Controller (MVC) architecture. Since this simple project does not need an external data repository, it does not contain a `models` directory, but this is where you would add it. +1. The `public` subdirectory contains directories for `images`, `javascripts`, and `stylesheets`. +1. The `conf` directory contains application configuration. For details on the rest of the project's structure see [[Anatomy of a Play Application|Anatomy]]. + +To locate the controller action method, open `app/controllers/HomeController.java` (or `.scala`) file with your favorite text editor. The `Homecontroller` class includes the `index` action method, as shown below. This is a very simple action method that generate an HTML page from the `index.scala.html` Twirl template file. + +Java +: +@[hello-world-index-action](code/javaguide/hello/HelloController.java) + +Scala +: +@[hello-world-index-action](code/scalaguide/hello/HelloController.scala) + +To view the route that maps the browser request to the controller method, open the `conf/routes` file. A route consists of an HTTP method, a path, and an action. This control over the URL schema makes it easy to design clean, human-readable, bookmarkable URLs. The following line maps a GET request for the root URL `/` to the `index` action in `HomeController`: + +@[hello-world-index-route](code/routes) + +Open `app/views/index.scala.html` with your text editor. The main directive in this file calls the main template `main.scala.html` with the string Welcome to generate the page. You can open `app/views/main.scala.html` to see how a `String` parameter sets the page title. + +With this overview of the tutorial application, you are ready to add a "Hello World" greeting. \ No newline at end of file diff --git a/manual/tutorial/Tutorials.md b/manual/tutorial/Tutorials.md new file mode 100644 index 00000000..cbbd862f --- /dev/null +++ b/manual/tutorial/Tutorials.md @@ -0,0 +1,217 @@ + +# Play Tutorials + +Play's documentation shows the available features and how to use them, but the documentation will not show how to create an application from start to finish. This is where tutorials and examples come in. + +Tutorials and examples are useful for showing a single application at work, especially when it comes to integrating with other systems such as databases or Javascript frameworks. + +The Play team uses [Lightbend Tech Hub](https://developer.lightbend.com/start/?group=play) to publish tutorials that cover a huge number of cases. There you can find projects in Java, Scala and for multiple versions of Play. You can pick one that demonstrates functionality of interest to you. The examples you can download cover the following topics: + +### Java + +| Example | Repository | +|:------------------------------------------|:-----------------------------------------------------------------------------------| +| REST API Example | [GitHub](https://github.com/playframework/play-java-rest-api-example/tree/2.6.x) | +| File Upload Example | [GitHub](https://github.com/playframework/play-java-fileupload-example/tree/2.6.x) | +| Forms Example | [GitHub](https://github.com/playframework/play-java-forms-example/tree/2.6.x) | +| JPA Example | [GitHub](https://github.com/playframework/play-java-jpa-example/tree/2.6.x) | +| Ebean Example | [GitHub](https://github.com/playframework/play-java-ebean-example/tree/2.6.x) | +| Websocket Example | [GitHub](https://github.com/playframework/play-java-websocket-example/tree/2.6.x) | +| Chatroom using Websockets Example | [GitHub](https://github.com/playframework/play-java-chatroom-example/tree/2.6.x) | +| Streaming Example | [GitHub](https://github.com/playframework/play-java-streaming-example/tree/2.6.x) | +| Compile Time Dependency Injection Example | [GitHub](https://github.com/playframework/play-java-compile-di-example/tree/2.6.x) | +| Using Dagger 2 for Compile Time DI | [GitHub](https://github.com/playframework/play-java-dagger2-example/tree/2.6.x) | + +### Scala + +| Example | Repository | +|:-------------------------------------------|:--------------------------------------------------------------------------------------------| +| REST API Example | [GitHub](https://github.com/playframework/play-scala-rest-api-example/tree/2.6.x) | +| File Upload Example | [GitHub](https://github.com/playframework/play-scala-fileupload-example/tree/2.6.x) | +| Forms Example | [GitHub](https://github.com/playframework/play-scala-forms-example/tree/2.6.x) | +| Anorm Example | [GitHub](https://github.com/playframework/play-scala-anorm-example/tree/2.6.x) | +| Integrated Slick Example | [GitHub](https://github.com/playframework/play-scala-slick-example/tree/2.6.x) | +| Isolated Slick Example | [GitHub](https://github.com/playframework/play-scala-isolated-slick-example/tree/2.6.x) | +| Websocket Example | [GitHub](https://github.com/playframework/play-scala-websocket-example/tree/2.6.x) | +| Chatroom using Websockets Example | [GitHub](https://github.com/playframework/play-scala-chatroom-example/tree/2.6.x) | +| Streaming Example | [GitHub](https://github.com/playframework/play-scala-streaming-example/tree/2.6.x) | +| Compile Time Dependency Injection Example | [GitHub](https://github.com/playframework/play-scala-compile-di-example/tree/2.6.x) | +| Dependency Injection using Macwire Example | [GitHub](https://github.com/playframework/play-scala-macwire-di-example/tree/2.6.x) | +| Secure Session Example | [GitHub](https://github.com/playframework/play-scala-secure-session-example/tree/2.6.x) | + +## Third Party Tutorials and Templates + +The Play community also has a number of tutorials and templates that cover aspects of Play than the documentation can, or has a different angle. Templates listed here are not maintained by the Play team, and so may be out of date. + +This is an incomplete list of several helpful blog posts, and because some of the blog posts have been written a while ago, this section is organized by Play version. + +### 2.6.x + +#### Play Framework Tutorials and other contents + +* [Running Play on GraalVM](https://blog.playframework.com/play-on-graal/): Play's core contributor Christian Schmitt explains how to run Play applications using [GraalVM](https://www.graalvm.org/) and the challenges and benefits of using GraalVM with Play. +* [Getting Started With Play Framework](https://dzone.com/refcardz/getting-started-play-framework): This DZone's reference card shows the most basic concepts of Play in a resumed but very informative way. +* [Play: The Missing Tutorial](https://github.com/shekhargulati/play-the-missing-tutorial/blob/master/01-hello-world.md): In this tutorial series, Shekhar Gulati + shows how to build a blogging platform called blogy that you can use to write and publish blogs. +* [Our adventure using Play Framework with Kotlin](https://blog.karumi.com/our-adventure-using-play-framework-in-kotlin/): This article written by [Antonio López Marín](http://tonilopezmr.github.io/) for [Karumi](https://www.karumi.com/) details the steps necessary to write a Play application using Kotlin language. +* [Add Authentication to Play Framework with OIDC and Okta](https://developer.okta.com/blog/2017/10/31/add-authentication-to-play-framework-with-oidc): [Matt Raible](https://twitter.com/mraible) shows how easy it is to integrate Play with a modern authentication mechanism like OpenID Connect using [play-pac4j](https://github.com/pac4j/play-pac4j). +* [REST API using Play Framework with Java](http://softwaredevelopercentral.blogspot.com/2017/10/rest-api-using-play-framework-with-java.html): This article shows how to create an application using Play Framework and Java with `GET`, `POST`, `PUT` and `DELETE` APIs for CRUD operations. +* [RESTful APIs With the Play Framework - Part 1](https://dzone.com/articles/restful-apis-with-play-framework-part-1) & [RESTful APIs With the Play Framework — Part 2](https://dzone.com/articles/restful-apis-with-play-frameworkpartnbsp2): In this two part tutorial, [Mercedes Wyss](https://twitter.com/itrjwyss) gives a look into how to set up your development environment using the Play framework, and how to get Play going on your machine, and later at creating RESTful APIs exploring how to handle JSON in your code. +* [Creating forms on your Play application - Part 1](https://pedrorijo.com/blog/play-forms/) & [Creating forms on your Play application - Part 2](https://pedrorijo.com/blog/advanced-play-forms/): Pedro Rijo goes from basic to advanced examples showing the helpers that Play provides when dealing with HTML forms, how to validate some inputs, and how does Play deals with those input errors. +* [React with Play Framework 2.6.x](https://medium.com/@yohan.gz/react-with-play-framework-2-6-x-a6e15c0b7bd): Yohan Gomez explains the pros and cons of different approaches when integrating React and Play, and later how to structure your project when using both. There are seed projects for both Java and Scala. +* [Angular 6 with Play Framework 2.6.x](https://medium.com/@yohan.gz/https-medium-com-yohan-gz-angular-with-play-framework-a6c3f8b339f3): Again Yohan Gomez explains how to integrate Play and modern frontend frameworks, but this time with Angular 6. There are seed projects for both Java and Scala. +* [Internationalization with Play Framework](https://blog.knoldus.com/internationalization-with-play-framework2-6-x/): Teena Vashist demonstrate how your application can support different languages using Play Framework 2.6. +* [Authentication using Actions in Play Framework](https://blog.knoldus.com/authentication-using-actions-in-play-framework/): Geetika Gupta demonstrates how to use Action Composition to handle authentication in Play applications. +* [Streaming data from PostgreSQL using Akka Streams and Slick in Play Framework](https://blog.knoldus.com/streaming-data-from-postgresql-using-akka-streams-and-slick-in-play-framework/): In this blog post, Sidharth Khattri explains the process wherein you can stream data directly from PostgreSQL database using Scala Slick (which is Scala’s database access/query library) and Akka Streams. +* [Stream a file to AWS S3 using Akka Streams (via Alpakka) in Play Framework](https://blog.knoldus.com/stream-a-file-to-aws-s3-using-akka-streams-via-alpakka-in-play-framework/): In this blog post Sidharth Khattri explains how a file can be streamed from a client (eg: browser) to Amazon S3 using [Alpakka's](https://developer.lightbend.com/docs/alpakka/current/) AWS [S3 connector](https://developer.lightbend.com/docs/alpakka/current/s3.html). + +### 2.5.x + +#### Play Framework Tutorial Video Series + +A tutorial video series by Radix Code provides an initial overview to Play, walking through initial IDE setup, defining routes, creating a CRUD application, enabling ORM support, and customizing the views with bootstrap. + +* [Debug Play Application in IntelliJ IDE](https://www.youtube.com/watch?v=RVKU9JvZmao) +* [Debug Play Application in Eclipse IDE](https://www.youtube.com/watch?v=f9TQD_V7rLg) +* [How Routing Works](https://www.youtube.com/watch?v=SnQQYl4xsN8) +* [Add Support for MySQL in Play](https://www.youtube.com/watch?v=J22fr8gQn2c) +* [Include Bootstrap and jQuery](https://www.youtube.com/watch?v=XyoZnTBUM5I) +* [Form Validations](https://www.youtube.com/watch?v=Wec-mbjQsrk) +* [Creating Custom Error Pages](https://www.youtube.com/watch?v=nhKpMrT2EZA) + +#### Dependency Injection + +* [Dependency Injection in Play Framework using Scala](https://www.schibsted.pl/blog/dependency-injection-play-framework-scala/) by Krzysztof Pado. + +#### Akka Streams + +* [Akka Streams integration in Play Framework 2.5](https://loicdescotte.github.io/posts/play25-akka-streams/) by Loïc Descotte. +* [Playing with Akka Streams and Twitter](https://loicdescotte.github.io/posts/play-akka-streams-twitter/) by Loïc Descotte. + +#### Database + +* [Play Database Application using Slick, Bootstrap](https://www.lightbend.com/activator/template/activator-play-slick-app): This is an example project for showcasing best practices and providing a seed for starting with Play & Slick, By [Knoldus](https://www.knoldus.com/home.knol). + +#### Forms and Validators + +* [Controller Forms](http://queirozf.com/entries/play2-scala-forms-and-validations): This provides examples of using forms and custom validators within a controller. +* [Json Validators](http://queirozf.com/entries/fully-customized-json-validator-for-play-framework-2): This guide lists methods of validating json against a customized case class or trait. + +#### REST APIs + +* [Making a REST API in Play](https://github.com/playframework/play-rest-api), a multi-part guide using the Scala API, by the Lightbend Play Team. +* [Play API REST Template](https://github.com/adrianhurt/play-api-rest-seed) by Adrianhurt: shows how to implement a complete Json RESTful API with some characteristics such as Authentication Token, pagination, filtering, sorting and searching and optional enveloping. + +#### Sub-projects + +* [Play Multidomain Seed](https://github.com/adrianhurt/play-multidomain-seed) by Adrianhurt: tries to be a skeleton for a simple multidomain project (www.myweb.com and admin.myweb.com). It shows you how to use subprojects for that and how to share common code. It is also ready to use with Webjars, CoffeeScript, LESS, RequireJS, assets Gzip and assets fingerprinting. Please, check the readme file for more details. +* [Play Multidomain Auth](https://github.com/adrianhurt/play-multidomain-auth) by Adrianhurt: this is a second part of play-multidomain-seed project. This project tries to be an example of how to implement an Authentication and Authorization layer using the Silhouette authentication library. It also uses [Play-Bootstrap](https://adrianhurt.github.io/play-bootstrap/) for easy template scaffolding. + +#### Upgrading + +* [Upgrading from Play 2.3 to Play 2.5](https://www.lucidchart.com/techblog/2017/02/22/upgrading-play-framework-2-3-play-2-5/) by Gregg Hernandez: Learn how to deal with common problems when upgrading to Play 2.5, including maintaining legacy behavior, transitioning to Akka Streams, and implementing compile-time dependency injection. + +### 2.4.x + +#### Semisafe + +Semisafe has an excellent series on Play in general: + +* [Templates, Routes and AJAX](http://semisafe.com/coding/2015/03/31/play_basics_templates_and_ajax.html) +* [Upgrading the Framework](http://semisafe.com/coding/2015/06/01/play_basics_upgrading_the_framework.html) +* [Database Access](http://semisafe.com/coding/2015/06/12/play_basics_database_access.html) +* [Async Futures and Actors](http://semisafe.com/coding/2015/06/22/play_basics_async_futures_and_actors.html) +* [Optimistic Future Composition](http://semisafe.com/coding/2015/07/14/play_basics_optimistic_future_composition.html) +* [React UI Coffeescript](http://semisafe.com/coding/2015/07/24/play_basics_ui_react_coffeescript.html) +* [CSRF Protection](http://semisafe.com/coding/2015/08/03/play_basics_csrf_protection.html) + +#### Minimal Play + +* [A Play Application in 38 Lines](https://beachape.com/blog/2015/07/25/slim-play-app/) by Lloyd Chan, showing a "Sinatra" style of Play application. + +#### Dependency Injection + +* [Playframework 2.4 Dependency Injection (DI)](http://mariussoutier.com/blog/2015/12/06/playframework-2-4-dependency-injection-di/) by Marius Soutier. +* [Testing with Dependency Injection](http://www.michaelpollmeier.com/2015/09/25/playframework-guice-di) by Michael Pollmeier. +* [Compile Time Dependency Injection with Play 2.4](https://loicdescotte.github.io/posts/play24-compile-time-di/) by Loïc Descotte. + +#### REST APIs + +Justin Rodenbostel of SPR Consulting also has two blog posts on building REST APIs in Play: + +* [Building a Simple REST API with Scala & Play! (PART 1)](https://spr.com/building-a-simple-rest-api-with-scala-play-part-1/) +* [Building a Simple REST API with Scala & Play! (PART 2)](https://spr.com/building-a-simple-rest-api-with-scala-play-part-2/) + +#### Slick + +* [Play framework, Slick and MySQL Tutorial](https://pedrorijo.com/blog/play-slick/) by Pedro Rijo. + +#### RethinkDB + +* [A classic CRUD application with Play 2.4.x, Scala and RethinkDB](https://rklicksolutions.wordpress.com/2016/02/03/play-2-4-x-rethinkdb-crud-application/) by [Rklick](https://github.com/rklick-solutions). + +#### Forms + +* [How to add a form to a Play application](https://www.theguardian.com/info/developer-blog/2015/dec/30/how-to-add-a-form-to-a-play-application) by Chris Birchall of the Guardian. + +#### EmberJS + +* [HTML 5 Device Orientation with play, ember and websockets](https://www.cakesolutions.net/teamblogs/go-reactive-activator-contest-reactive-orientation) by Cake Solutions (with [activator template](https://www.lightbend.com/activator/template/reactive-orientation)). + +#### AngularJS, RequireJS and sbt-web + +Marius Soutier has an excellent series on setting up a Javascript interface using AngularJS with Play and sbt-web. It was originally written for Play 2.1.x, but has been updated for Play 2.4.x. + +* [RequireJS Optimization with Play 2.1 and WebJars](http://mariussoutier.com/blog/2013/08/25/requirejs-optimization-play-webjars/) +* [Intro to sbt-web](http://mariussoutier.com/blog/2014/10/20/intro-sbt-web/) +* [Understanding sbt and sbt-web settings](http://mariussoutier.com/blog/2014/12/07/understanding-sbt-sbt-web-settings/) +* [Play Angular Require Seed Updates](http://mariussoutier.com/blog/2015/07/25/play-angular-require-seed-updates/) + +#### React JS + +* [ReactJS Tutorial with Play, Scala and WebJars](http://ticofab.io/react-js-tutorial-with-play_scala_webjars/) by Fabio Tiriticco. +* [A basic example to render UI using ReactJS with Play 2.4.x, Scala and Anorm](https://blog.knoldus.com/2015/07/19/playing-reactjs/) by Knoldus / [activator template](https://github.com/knoldus/playing-reactjs#master). + +### 2.3.x + +#### REST APIs + +* [Playing with Play Framework 2.3.x: REST, pipelines, and Scala](https://shinesolutions.com/2015/04/21/playing-with-play-framework-2-3-x-rest-pipelines-and-scala/) by Sampson Oliver. + +#### Anorm + +Knoldus has a nice series of blog posts on Anorm: + +* [Employee-Self-Service – Building Reactive Play application with Anorm SQL data access – (Part-1)](https://blog.knoldus.com/2014/03/24/employee-self-service-building-reactive-play-application-with-anorm-sql-data-access/) +* [Employee-Self-Service – Building Reactive Play application with Anorm SQL data access – (Part-2)](https://blog.knoldus.com/2014/03/31/employee-self-service-2/) +* [Employee-Self-Service: Reactive and Non-Blocking Database Access using Play Framework and Anorm – (Part-3)](https://blog.knoldus.com/2014/04/06/employee-self-service-3/) +* [Employee-Self-Service: Reactive and Non-Blocking Database Access using Play Framework and Anorm – (Part-4)](https://blog.knoldus.com/2014/04/13/employee-self-service-reactive-and-non-blocking-database-access-using-play-framework-and-anorm-part-4/) + +#### Forms + +* [Example form including multiple checkboxes and selection](https://ics-software-engineering.github.io/play-example-form/) by Philip Johnson. +* [UX-friendly conditional form mapping in Play](http://ntcoding.com/blog/2016/02/play-framework-conditional-form-mappings) by Nick Tune. + +### 2.2.x + +#### Advanced Routing + +* [Advanced routing in Play Framework](https://jazzy.id.au/2013/05/08/advanced_routing_in_play_framework.html) by James Roper. +* [Play Routes – Part 1, Basics](http://mariussoutier.com/blog/2012/12/10/playframework-routes-part-1-basics/) by Marius Soutier. +* [Play Routes – Part 2, Advanced Use Cases](http://mariussoutier.com/blog/2012/12/11/playframework-routes-part-2-advanced/) by Marius Soutier. + +#### Path Bindables + +* [How to implement a custom PathBindable with Play 2](http://julien.richard-foy.fr/blog/2012/04/09/how-to-implement-a-custom-pathbindable-with-play-2/) by Julien Richard-Foy. + +#### Templates + +* [Play Framework 2.0 Templates – Part 1, Parameters](http://mariussoutier.com/blog/2012/04/27/play-framework-2-0-templates-part-1-parameters/) by Marius Soutier. + +#### User Interface + +* [Composite user interface without boilerplate using Play 2](http://julien.richard-foy.fr/blog/2012/02/26/composite-user-interface-without-boilerplate-using-play-2/) by Julien Foy. + +#### Play in Practice + +* [Play in Practice](https://tersesystems.com/2013/04/20/play-in-practice/) by Will Sargent. diff --git a/manual/tutorial/code/javaguide/hello/HelloController.java b/manual/tutorial/code/javaguide/hello/HelloController.java new file mode 100644 index 00000000..47cb7112 --- /dev/null +++ b/manual/tutorial/code/javaguide/hello/HelloController.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.hello; + +import play.mvc.*; + +public class HelloController extends Controller { + + // #hello-world-index-action + public Result index() { + // ###replace: ok(views.html.index.render()); + return ok(javaguide.hello.html.index.render()); + } + // #hello-world-index-action + + // #hello-world-hello-action + public Result hello() { + // ###replace: return ok(views.html.hello.render()); + return ok(javaguide.hello.html.hello.render()); + } + // #hello-world-hello-action + + /* + //#hello-world-hello-error-action + public Result hello(String name) { + return ok(views.html.hello.render()); + } + //#hello-world-hello-error-action + */ + + // #hello-world-hello-correct-action + public Result hello(String name) { + // ###replace: return ok(views.html.hello.render(name)); + return ok(javaguide.hello.html.helloName.render(name)); + } + // #hello-world-hello-correct-action +} diff --git a/manual/tutorial/code/javaguide/hello/hello.scala.html b/manual/tutorial/code/javaguide/hello/hello.scala.html new file mode 100644 index 00000000..87e2b7f2 --- /dev/null +++ b/manual/tutorial/code/javaguide/hello/hello.scala.html @@ -0,0 +1,9 @@ +@* #hello-world-page *@ +@main("Hello") { +
+
+

Hello World

+
+
+} +@* #hello-world-page *@ \ No newline at end of file diff --git a/manual/tutorial/code/javaguide/hello/helloName.scala.html b/manual/tutorial/code/javaguide/hello/helloName.scala.html new file mode 100644 index 00000000..e894a33a --- /dev/null +++ b/manual/tutorial/code/javaguide/hello/helloName.scala.html @@ -0,0 +1,9 @@ +@(name: String) +@main("Hello") { +
+
+

Hello, @name

+
+
+} +@* #hello-world-page *@ \ No newline at end of file diff --git a/manual/tutorial/code/javaguide/hello/index.scala.html b/manual/tutorial/code/javaguide/hello/index.scala.html new file mode 100644 index 00000000..15a6e995 --- /dev/null +++ b/manual/tutorial/code/javaguide/hello/index.scala.html @@ -0,0 +1,3 @@ +@main("Hello") { +

Index view

+} \ No newline at end of file diff --git a/manual/tutorial/code/javaguide/hello/main.scala.html b/manual/tutorial/code/javaguide/hello/main.scala.html new file mode 100644 index 00000000..e4385b6c --- /dev/null +++ b/manual/tutorial/code/javaguide/hello/main.scala.html @@ -0,0 +1,11 @@ +@(title: String)(content: play.twirl.api.Html) + + + + + @title + + + @content + + diff --git a/manual/tutorial/code/routes b/manual/tutorial/code/routes new file mode 100644 index 00000000..d18b32d5 --- /dev/null +++ b/manual/tutorial/code/routes @@ -0,0 +1,11 @@ +# #hello-world-index-route +GET / controllers.HomeController.index +# #hello-world-index-route + +# #hello-world-hello-route +GET /hello controllers.HomeController.hello +# #hello-world-hello-route + +# #hello-world-hello-name-route +# ###insert: GET /hello controllers.HomeController.hello(name: String) +# #hello-world-hello-name-route \ No newline at end of file diff --git a/manual/tutorial/code/scalaguide/hello/HelloController.scala b/manual/tutorial/code/scalaguide/hello/HelloController.scala new file mode 100644 index 00000000..1f49d4d3 --- /dev/null +++ b/manual/tutorial/code/scalaguide/hello/HelloController.scala @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package scalaguide.hello { + import play.api.mvc._ + import javax.inject.Inject + + package views { + + import play.twirl.api.Html + + object html { + def index(): Html = Html("Index page") + def hello(): Html = Html("Hello page") + def hello(name: String): Html = Html(s"Hello $name") + } + } + + class HelloController @Inject()(val controllerComponents: ControllerComponents) extends BaseController { + + //#hello-world-index-action + def index = Action { + Ok(views.html.index()) + } + //#hello-world-index-action + + //#hello-world-hello-action + def hello = Action { + Ok(views.html.hello()) + } + //#hello-world-hello-action + + /* + //#hello-world-hello-error-action + def hello(name: String) = Action { + Ok(views.html.hello()) + } + //#hello-world-hello-error-action + */ + + //#hello-world-hello-correct-action + def hello(name: String) = Action { + Ok(views.html.hello(name)) + } + //#hello-world-hello-correct-action + } +} diff --git a/manual/tutorial/images/hello-error.png b/manual/tutorial/images/hello-error.png new file mode 100644 index 00000000..381fde2a Binary files /dev/null and b/manual/tutorial/images/hello-error.png differ diff --git a/manual/tutorial/images/hello-name.png b/manual/tutorial/images/hello-name.png new file mode 100644 index 00000000..7e6c1659 Binary files /dev/null and b/manual/tutorial/images/hello-name.png differ diff --git a/manual/tutorial/images/hello-page.png b/manual/tutorial/images/hello-page.png new file mode 100644 index 00000000..d9009833 Binary files /dev/null and b/manual/tutorial/images/hello-page.png differ diff --git a/manual/tutorial/images/play-request-response.png b/manual/tutorial/images/play-request-response.png new file mode 100644 index 00000000..03d8b946 Binary files /dev/null and b/manual/tutorial/images/play-request-response.png differ diff --git a/manual/tutorial/images/play-stack.png b/manual/tutorial/images/play-stack.png new file mode 100644 index 00000000..b4728d5f Binary files /dev/null and b/manual/tutorial/images/play-stack.png differ diff --git a/manual/tutorial/index.toc b/manual/tutorial/index.toc new file mode 100644 index 00000000..b86d9e6b --- /dev/null +++ b/manual/tutorial/index.toc @@ -0,0 +1,4 @@ +HelloWorldTutorial: Hello World Tutorial +PlayApplicationOverview: Play Application Overview +ImplementingHelloWorld: Implementing Hello World +Tutorials:Play Tutorials \ No newline at end of file diff --git a/manual/working/commonGuide/Modules.md b/manual/working/commonGuide/Modules.md new file mode 100644 index 00000000..e9a1f52b --- /dev/null +++ b/manual/working/commonGuide/Modules.md @@ -0,0 +1,22 @@ + +# Extending Play with modules + +At its core, Play is a very lightweight HTTP server, providing mechanisms for serving HTTP requests, but not much else. Additional functionality in Play is provided through the use of Play modules. + +## What is a module? + +There is no strict definition in Play of what a module is or isn't - a module could be just a library that provides some helper methods to help you do something, or it could be a full framework providing complex functionality such as user management. Some modules are built in to Play, others are written and maintained by members of the Play community. + +Some modules provide components - objects that represent resources, for example a database connection. These objects may have a lifecycle and need to be started and stopped when the application starts and stops, and they may hold some state such as a cache. Play provides a variety of mechanisms for accessing and using these components. Components are not only provided by modules, they may be provided by the application themselves. + +## Accessing modules + +One of the earliest decisions that you need to make when starting a new Play project is how you will access the components provided by modules. Components are accessed through the use of a dependency injection mechanism, where rather than having your components look up other components in the system, your components declare what other components they need, and the system injects those components into your components. + +At its core, Play is agnostic to any particular form of dependency injection, however out of the box Play provides and we recommend that you use [Guice](https://github.com/google/guice). The remainder of this documentation will assume that this is the decision that you have made, however there will be examples of how to integrate with other dependency injection mechanisms. + +You can read more about dependency injection in [[Scala|ScalaDependencyInjection]] or [[Java|JavaDependencyInjection]]. + +## Community modules + +Play has a list of [[community-developed modules|ModuleDirectory]] that may provide functionality you need or serve as examples of how to write a module. diff --git a/manual/working/commonGuide/assets/Assets.md b/manual/working/commonGuide/assets/Assets.md new file mode 100644 index 00000000..06a336fa --- /dev/null +++ b/manual/working/commonGuide/assets/Assets.md @@ -0,0 +1,11 @@ + +# Static assets + +This section covers serving your application’s static resources such as JavaScript, CSS and images. + +- [[Working with public assets|AssetsOverview]] +- [[Using CoffeeScript|AssetsCoffeeScript]] +- [[Using LESS CSS|AssetsLess]] +- [[Using Sass|AssetsSass]] +- [[Using JSHint|AssetsJSHint]] +- [[Using RequireJs|RequireJS-support]] diff --git a/manual/working/commonGuide/assets/AssetsCoffeeScript.md b/manual/working/commonGuide/assets/AssetsCoffeeScript.md new file mode 100644 index 00000000..04e15a53 --- /dev/null +++ b/manual/working/commonGuide/assets/AssetsCoffeeScript.md @@ -0,0 +1,39 @@ + +# Using CoffeeScript + +[CoffeeScript](https://coffeescript.org/) is a small and elegant language that compiles into JavaScript. It provides a nice syntax for writing JavaScript code. + +Compiled assets in Play must be defined in the `app/assets` directory. They are handled by the build process and CoffeeScript sources are compiled into standard JavaScript files. The generated JavaScript files are distributed as standard resources into the same `public/` folder as other unmanaged assets, meaning that there is no difference in the way you use them once compiled. + +For example a CoffeeScript source file `app/assets/javascripts/main.coffee` will be available as a standard JavaScript resource, at `public/javascripts/main.js`. + +CoffeeScript sources are compiled automatically during an `assets` command, or when you refresh any page in your browser while you are running in development mode. Any compilation errors will be displayed in your browser: + +[[images/coffeeError.png]] + +## Layout + +Here is an example layout for using CoffeeScript in your projects: + +``` +app + └ assets + └ javascripts + └ main.coffee +``` + +You can use the following syntax to use the compiled JavaScript file in your template: + +```html + +``` + +Note the `lib/requirejs/require.js` path. The `lib` folder denotes the extracted WebJar assets, the `requirejs` folder corresponds to the WebJar artifactId, and the `require.js` refers to the required asset at the root of the WebJar. To clarify, the `requirejs` webjar dependency is declared at your build file like: + +```scala +libraryDependencies += "org.webjars" % "requirejs" % "2.2.0" +``` + +## How are public assets packaged? + +During the build process, the contents of the `public` folder are processed and added to the application classpath. + +When you package your application, all assets for the application, including all sub projects, are aggregated into a single jar, in `target/my-first-app-1.0.0-assets.jar`. This jar is included in the distribution so that your Play application can serve them. This jar can also be used to deploy the assets to a CDN or reverse proxy. + +## The Assets controller + +Play comes with a built-in controller to serve public assets. By default, this controller provides caching, ETag, gzip and compression support. There are two different styles that the Assets controller supports: the first is to use Play's configuration, and the second is to use pass the assets path directly to the controller. + +### Binding the Assets components + +If you are using runtime dependency injection, Play already provides bindings in the `AssetsModule`, which is loaded by default. (If you are not using assets, you can disable this module by adding the configuration `play.modules.disabled += controllers.AssetsModule`.). The bindings there make `Assets` class injectable. + +If you are using components traits to do compile-time dependency injection, you should mix in `controllers.AssetsComponents`. Then the controller will be available as `assets: Assets`. You do not need to construct the controller yourself. + +### Using assets with configuration + +For the most common case where you only have one place where assets are centrally located, you can use configuration to specify the location: + +``` +play.assets { + path = "/public" + urlPrefix = "/assets" +} +``` + +And use the `Assets.at` method with one parameter: + +```scala +Assets.at(file: String) +``` + +Then in routes: + +@[assets-configured-path](code/configured.assets.routes) + +### Passing the assets path directly + +The `Assets` controller also defines an `at` action with two parameters: + +```scala +Assets.at(path: String, file: String) +``` + +The `path` parameter must be fixed and defines the directory managed by the action. The `file` parameter is usually dynamically extracted from the request path. + +Here is the typical mapping of the `Assets` controller in your `conf/routes` file: + +@[assets-wildcard](code/common.assets.routes) + +Note that we define the `*file` dynamic part that will match the `.*` regular expression. So for example, if you send this request to the server: + +``` +GET /assets/javascripts/jquery.js +``` + +The router will invoke the `Assets.at` action with the following parameters: + +``` +controllers.Assets.at("/public", "javascripts/jquery.js") +``` + +To route to a single static file, both the path and file have to be specified: + +@[assets-single-static-file](code/common.assets.routes) + +## Reverse routing for public assets + +As for any controller mapped in the routes file, a reverse controller is created in `controllers.routes.Assets`. You use this to reverse the URL needed to fetch a public resource. For example, from a template: + +```html + +``` + +In `DEV` mode this will by default produce the following result: + +```html + +``` + +If your app is not running in `DEV` mode **and** a `jquery.min.js` or `jquery-min.js` file exists then by default the minified file will be used instead: + +```html + +``` + +This makes debugging of JavaScript files easier during development. Of course this not only works for JavaScript files but for any file extension. +If you don't want Play to automatically resolve the `.min.*` or `-min.*` files, regardless of the mode your application is running in, you can set `play.assets.checkForMinified = false` in your `application.conf` (or to `true` to always resolve the min file, even in `DEV` mode). + +Note that we don’t specify the first `folder` parameter when we reverse the route. This is because our routes file defines a single mapping for the `Assets.at` action, where the `folder` parameter is fixed. So it doesn't need to be specified. + +However, if you define two mappings for the `Assets.at` action, like this: + +@[assets-two-mappings](code/common.assets.routes) + +You will then need to specify both parameters when using the reverse router: + +```html + + +``` + +## Reverse routing and fingerprinting for public assets + +[sbt-web](https://github.com/sbt/sbt-web) brings the notion of a highly configurable asset pipeline to Play e.g. in your build file: + +```scala +pipelineStages := Seq(rjs, digest, gzip) +``` + +The above will order the RequireJs optimizer ([sbt-rjs](https://github.com/sbt/sbt-rjs)), the digester ([sbt-digest](https://github.com/sbt/sbt-digest)) and then compression ([sbt-gzip](https://github.com/sbt/sbt-gzip)). Unlike many sbt tasks, these tasks will execute in the order declared, one after the other. + +In essence asset fingerprinting permits your static assets to be served with aggressive caching instructions to a browser. This will result in an improved experience for your users given that subsequent visits to your site will result in less assets requiring to be downloaded. Rails also describes the benefits of [asset fingerprinting](https://guides.rubyonrails.org/asset_pipeline.html#what-is-fingerprinting-and-why-should-i-care-questionmark). + +The above declaration of `pipelineStages` and the requisite `addSbtPlugin` declarations in your `plugins.sbt` for the plugins you require are your start point. You must then declare to Play what assets are to be versioned. + +There are two ways obtain the real path of a fingerprinted asset. The first way uses static state and supports the same style as normal reverse routing. It does so by looking up assets metadata that's set by a running Play application. The second way is to use configuration and inject an AssetsFinder to find your asset. + +### Using reverse routing and static state + +If you plan to use the reverse router with static state, the following routes file entry declares that all assets are to be versioned: + +```scala +GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) +``` + +> **Note:** Make sure you indicate that `file` is an asset by writing `file: Asset`. + +You then use the reverse router, for example within a `scala.html` view: + +```html + +``` + +The downside of this approach is that it requires special logic that converts the `Asset` from the path you passed in to the final minified path with a digest. It's also more difficult to unit test, since there's no component you can mock to define the path. + +### Using configuration and AssetsFinder + +You can also define your paths in configuration, and inject an `AssetsFinder` into your controller to get the final path. In your configuration set up the assets `path` (the directory containing assets) and the `urlPrefix` (the prefix to the URL in your application): + +``` +play.assets { + path = "/public" + urlPrefix = "/assets" +} +``` + +In your routes file you can define a route as follows: + +@[assets-configured-path-versioned](code/configured.assets.routes) + +(you should not use the `: Asset` type annotation here) + +Then you can pass an `AssetsFinder` to your template and use that to get the final path: + +```html +@(assetsFinder: AssetsFinder) + + +``` + +The advantage to this approach is that it requires no static state to set up. That means you can unit test your controllers and templates without a running application by simply passing an instance of `AssetsFinder`. That makes it simple to mock for a unit test by simply implementing the abstract methods that return `String`s. + +Using the `AssetsFinder` approach also makes it easy to run multiple self-contained applications at once in the same classloader, since it uses no static state. This can also be helpful for testing. + +The `AssetsFinder` interface also works in cases where fingerprinting is not used. It returns the original asset if a fingerprinted and/or minified asset cannot be found. + +## Etag support + +The `Assets` controller automatically manages **ETag** HTTP Headers. The ETag value is generated from the digest (if `sbt-digest` is being used in the asset pipeline) or otherwise the resource name and the file’s last modification date. If the resource file is embedded into a file, the JAR file’s last modification date is used. + +When a web browser makes a request specifying this **Etag** then the server can respond with **304 NotModified**. + +## Gzip support + +If a resource with the same name but using a `.gz` suffix is found then the `Assets` controller will also serve the latter and add the following HTTP header: + +``` +Content-Encoding: gzip +``` + +Including the `sbt-gzip` plugin in your build and declaring its position in the `pipelineStages` is all that is required to generate gzip files. + +## Additional `Cache-Control` directive + +Using Etag is usually enough for the purposes of caching. However if you want to specify a custom `Cache-Control` header for a particular resource, you can specify it in your `application.conf` file. For example: + +``` +# Assets configuration +# ~~~~~ +play.assets.cache."/public/stylesheets/bootstrap.min.css"="max-age=3600" +``` + +You can also use partial paths to specify a custom `Cache-Control` for all the assets that are under that path, for example: + +``` +# Assets configuration +# ~~~~~ +play.assets.cache."/public/stylesheets/"="max-age=100" +play.assets.cache."/public/javascripts/"="max-age=200" +``` + +And Play will use `max-age=200` for all assets under `/public/javascripts` (like `/public/javascripts/main.js`) and `max-age=100` to all assets under `/public/stylesheets` (like `/public/stylesheets/main.css`). + +### How the additional directives are applied + +Play sorts the `Cache-Control` directives lexicographically and later from more specific to less specific. For example, given the following configuration: + +``` +# 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" +``` + +The directives will be sorted and applied in the following order: + +``` +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" +``` + +> **Note:** a configuration like `play.assets.cache."/public/stylesheets"="max-age=101"` will match both `public/stylesheets.css` and `public/stylesheets/main.css`, so you may want to add a trailing `/` to better differentiate directories, for example `play.assets.cache."/public/stylesheets/"="max-age=101"`. + +## Managed assets + +Starting with Play 2.3 managed assets are processed by [sbt-web](https://github.com/sbt/sbt-web#sbt-web) based plugins. Prior to 2.3 Play bundled managed asset processing in the form of CoffeeScript, LESS, JavaScript linting (ClosureCompiler) and RequireJS optimization. The following sections describe sbt-web and how the equivalent 2.2 functionality can be achieved. Note though that Play is not limited to this asset processing technology as many plugins should become available to sbt-web over time. Please check-in with the [sbt-web](https://github.com/sbt/sbt-web#sbt-web) project to learn more about what plugins are available. + +Many plugins use sbt-web's [js-engine plugin](https://github.com/sbt/sbt-js-engine). js-engine is able to execute plugins written to the Node API either within the JVM via the excellent [Trireme](https://github.com/apigee/trireme#trireme) project, or directly on [Node.js](https://nodejs.org/) for superior performance. Note that these tools are used during the development cycle only and have no involvement during the runtime execution of your Play application. If you have Node.js installed then you are encouraged to declare the following environment variable. For Unix, if `SBT_OPTS` has been defined elsewhere then you can: + +```bash +export SBT_OPTS="$SBT_OPTS -Dsbt.jse.engineType=Node" +``` + +The above declaration ensures that Node.js is used when executing any sbt-web plugin. + +## Range requests support + +`Assets` controller automatically supports part of [RFC 7233](https://tools.ietf.org/html/rfc7233) which defines how range requests and partial responses works. The `Assets` controller will delivery a `206 Partial Content` if a satisfiable `Range` header is present in the request. It will also returns a `Accept-Ranges: bytes` for all assets delivery. + +> **Note:** Besides the fact that some parsing is done to better handle multiple ranges, `multipart/byteranges` is not fully supported yet. + +You can also return `206 Partial Content` when delivering files without using the `Assets` controller: + +### Scala version + +@[range-request](code/assets/controllers/RangeRequestController.scala) + +### Java version + +@[range-request](code/assets/controllers/JavaRangeRequestController.java) + +Both examples will delivery just part of the video file, according to the requested range. diff --git a/manual/working/commonGuide/assets/AssetsSass.md b/manual/working/commonGuide/assets/AssetsSass.md new file mode 100644 index 00000000..00f6cb1d --- /dev/null +++ b/manual/working/commonGuide/assets/AssetsSass.md @@ -0,0 +1,95 @@ + +# Using Sass + +[Sass](http://sass-lang.com/) is a dynamic stylesheet language. It allows considerable flexibility in the way you write CSS files including support for variables, mixins and more. + +Compilable assets in Play are typically defined in the `app/assets` directory. They are handled by the build process, and Sass sources are compiled into standard CSS files. The generated CSS files are distributed as standard resources into the same `public/` folder as the unmanaged assets, meaning that there is no difference in the way you use them once compiled. + +For example, Sass source file `app/assets/stylesheets/main.scss` will be available as standard CSS resource, at `public/stylesheets/main.css`. + +Sass sources are compiled automatically during an `assets` command, or when you refresh any page in your browser while you are running in development mode. Any compilation errors will be displayed in your browser: + +[[images/sassError.png]] + +## Working with partial Sass source files + +Any Sass file (`*.scss`/`*.sass`) will automatically be compiled. The Sass plugin will automatically determine which Sass syntax is being used (indented or not) based on the filename. A file who's name starts with an `_` will not be compiled separately. However, such files can be included in other Sass files by using the standard Sass import feature. + +## Layout + +Below an example layout for using Sass in your project is given: + +``` +app + └ assets + └ stylesheets + └ main.scss + └ utils + └ _reset.scss + └ _layout.scss +``` + +Given the following `main.scss` source: + +```scss +@import "utils/reset"; +@import "utils/layout"; + +h1 { + color: red; +} +``` + +The Sass file outlined above, will be compiled into `public/stylesheets/main.css`. Hence, this file can be used in your template as any regular public asset: + +```html + +``` + +## Mixing Sass and web-jars + +[WebJars](https://www.webjars.org) enable us to depend on client libraries without pulling all dependencies into our own code base manually. + +Compass is a library containing all sorts of reusable functions and mixins for Sass. Unfortunately, this library is targeted towards the Ruby implementation of Sass. There is a number of useful mixins that can be extracted from it. Fortunately, these mixins are wrapped in a web-jar. + +To include these compass mixins in your project is as easy as including the web-jar dependency in your library dependencies. For example, within a `build.sbt` file: + +```scala +libraryDependencies += "org.webjars.bower" % "compass-mixins" % "0.12.7" +``` + +sbt-web will automatically extract WebJars into a `lib` directory relative to your asset's target directory. Therefore to use the Compass mixins you can import the mixins by: + +```scss +@import "lib/compass-mixins/lib/compass"; + +table.ellipsed-table { + tr td { + max-width: 100px; + @include ellipsis(); + } +} +``` + +The same idea can be used to include other Sass libraries, for instance the [official Sass port of bootstrap](https://github.com/twbs/bootstrap-sass). To include the WebJar use: + +```scala +libraryDependencies += "org.webjars.bower" % "bootstrap-sass" % "3.3.6" +``` + +Then to use it in your project, you can use: + +```scss +@import "lib/bootstrap-sass/assets/stylesheets/bootstrap"; +``` + +## Enablement and Configuration + +Sass compilation is enabled by simply adding the sbt-sassify plugin to your plugins.sbt file when using the `PlayJava` or `PlayScala` plugins: + +```scala +addSbtPlugin("org.irundaia.sbt" % "sbt-sassify" % "1.4.11") +``` + +The plugin's default configuration should normally be sufficient. However please refer to the [plugin's documentation](https://github.com/irundaia/sbt-sassify#options) for information on how it may be configured as well as its latest version. + diff --git a/manual/working/commonGuide/assets/RequireJS-support.md b/manual/working/commonGuide/assets/RequireJS-support.md new file mode 100644 index 00000000..44f69f26 --- /dev/null +++ b/manual/working/commonGuide/assets/RequireJS-support.md @@ -0,0 +1,40 @@ + +# RequireJS + +According to [RequireJS](https://requirejs.org/)' website + +> RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments... Using a modular script loader like RequireJS will improve the speed and quality of your code. + +What this means in practice is that one can use [RequireJS](https://requirejs.org/) to modularize your JavaScript. RequireJS achieves this by implementing a semi-standard API called [Asynchronous Module Definition](http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition) (other similar ideas include [CommonJS](http://www.commonjs.org/) ). Using AMD makes it is possible to resolve and load javascript modules on the _client side_ while allowing server side _optimization_. For server side optimization module dependencies may be minified and combined using [UglifyJS 2](https://github.com/mishoo/UglifyJS2#uglifyjs-2). + +By convention RequireJS expects a main.js file to bootstrap its module loader. + +## Deployment + +The RequireJS optimizer shouldn't generally kick-in until it is time to perform a deployment i.e. by running the `start`, `stage` or `dist` tasks. + +If you're using WebJars with your build then the RequireJS optimizer plugin will also ensure that any JavaScript resources referenced from within a WebJar are automatically referenced from the [jsdelivr](https://www.jsdelivr.com) CDN. In addition if any `.min.js` file is found then that will be used in place of `.js`. An added bonus here is that there is no change required to your html! + +## Enablement and Configuration + +RequireJS optimization is enabled by simply adding the plugin to your plugins.sbt file when using the `PlayJava` or `PlayScala` plugins: + +```scala +addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.9") +``` + +To add the plugin to the asset pipeline you can declare it as follows (assuming just the one plugin for the pipeline - add others into the sequence such as digest and gzip as required): + +```scala +pipelineStages := Seq(rjs) +``` + +A standard build profile for the RequireJS optimizer is provided and should suffice for most projects. However please refer to the [plugin's documentation](https://github.com/sbt/sbt-rjs#sbt-rjs) for information on how it may be configured. + +Note that RequireJS performs a lot of work and while it works when executed in-JVM under Trireme, you will be best to use Node.js as the js-engine from a performance perspective. For convenience you can set the `sbt.jse.engineType` property in `SBT_OPTS`. For example on Unix: + +```bash +export SBT_OPTS="$SBT_OPTS -Dsbt.jse.engineType=Node" +``` + +Please refer to the [plugin's documentation](https://github.com/sbt/sbt-rjs#sbt-rjs) for information on how it may be configured. diff --git a/manual/working/commonGuide/assets/code/CommonAssets.scala b/manual/working/commonGuide/assets/code/CommonAssets.scala new file mode 100644 index 00000000..729e5001 --- /dev/null +++ b/manual/working/commonGuide/assets/code/CommonAssets.scala @@ -0,0 +1,8 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package common.assets + +package object controllers { + type Assets = _root_.controllers.Assets +} diff --git a/manual/working/commonGuide/assets/code/ConfiguredAssets.scala b/manual/working/commonGuide/assets/code/ConfiguredAssets.scala new file mode 100644 index 00000000..a7489ffc --- /dev/null +++ b/manual/working/commonGuide/assets/code/ConfiguredAssets.scala @@ -0,0 +1,8 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package configured.assets + +package object controllers { + type Assets = _root_.controllers.Assets +} diff --git a/manual/working/commonGuide/assets/code/assets/controllers/JavaRangeRequestController.java b/manual/working/commonGuide/assets/code/assets/controllers/JavaRangeRequestController.java new file mode 100644 index 00000000..12d4eea6 --- /dev/null +++ b/manual/working/commonGuide/assets/code/assets/controllers/JavaRangeRequestController.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package assets.controllers; + +import play.mvc.*; + +import java.io.File; + +public class JavaRangeRequestController extends Controller { + + // #range-request + public Result video(Long videoId) { + File videoFile = getVideoFile(videoId); + return RangeResults.ofFile(videoFile); + } + // #range-request + + private File getVideoFile(Long videoId) { + return new File("video.mp4"); + } +} diff --git a/manual/working/commonGuide/assets/code/assets/controllers/RangeRequestController.scala b/manual/working/commonGuide/assets/code/assets/controllers/RangeRequestController.scala new file mode 100644 index 00000000..40153f53 --- /dev/null +++ b/manual/working/commonGuide/assets/code/assets/controllers/RangeRequestController.scala @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package assets.controllers + +import java.io.File +import javax.inject.Inject + +import play.api.mvc._ + +class RangeRequestController @Inject()(c: ControllerComponents) extends AbstractController(c) { + + // #range-request + def video(videoId: Long) = Action { implicit request => + val videoFile = getVideoFile(videoId) + RangeResult.ofFile(videoFile, request.headers.get(RANGE), Some("video/mp4")) + } + // #range-request + + private def getVideoFile(videoId: Long) = new File("video.mp4") +} diff --git a/manual/working/commonGuide/assets/code/common.assets.routes b/manual/working/commonGuide/assets/code/common.assets.routes new file mode 100644 index 00000000..9502fda6 --- /dev/null +++ b/manual/working/commonGuide/assets/code/common.assets.routes @@ -0,0 +1,13 @@ +#assets-wildcard +GET /assets/*file controllers.Assets.at(path="/public", file) +#assets-wildcard + +#assets-single-static-file +GET /favicon.ico controllers.Assets.at(path="/public", file="favicon.ico") +#assets-single-static-file + +#assets-two-mappings +GET /javascripts/*file controllers.Assets.at(path="/public/javascripts", file) +GET /images/*file controllers.Assets.at(path="/public/images", file) +#assets-two-mappings + diff --git a/manual/working/commonGuide/assets/code/configured.assets.routes b/manual/working/commonGuide/assets/code/configured.assets.routes new file mode 100644 index 00000000..898d3e24 --- /dev/null +++ b/manual/working/commonGuide/assets/code/configured.assets.routes @@ -0,0 +1,7 @@ +#assets-configured-path +GET /assets/*file controllers.Assets.at(file) +#assets-configured-path + +#assets-configured-path-versioned +GET /assets/*file controllers.Assets.versioned(file) +#assets-configured-path-versioned diff --git a/manual/detailedTopics/assets/images/ClosureError.png b/manual/working/commonGuide/assets/images/ClosureError.png similarity index 100% rename from manual/detailedTopics/assets/images/ClosureError.png rename to manual/working/commonGuide/assets/images/ClosureError.png diff --git a/manual/detailedTopics/assets/images/coffeeError.png b/manual/working/commonGuide/assets/images/coffeeError.png similarity index 100% rename from manual/detailedTopics/assets/images/coffeeError.png rename to manual/working/commonGuide/assets/images/coffeeError.png diff --git a/manual/detailedTopics/assets/images/lessError.png b/manual/working/commonGuide/assets/images/lessError.png similarity index 100% rename from manual/detailedTopics/assets/images/lessError.png rename to manual/working/commonGuide/assets/images/lessError.png diff --git a/manual/working/commonGuide/assets/images/sassError.png b/manual/working/commonGuide/assets/images/sassError.png new file mode 100644 index 00000000..b62d40f6 Binary files /dev/null and b/manual/working/commonGuide/assets/images/sassError.png differ diff --git a/manual/working/commonGuide/assets/index.toc b/manual/working/commonGuide/assets/index.toc new file mode 100644 index 00000000..41643251 --- /dev/null +++ b/manual/working/commonGuide/assets/index.toc @@ -0,0 +1,7 @@ +Assets:Static assets +AssetsOverview:Working with public assets +AssetsCoffeeScript:Using CoffeeScript +AssetsLess:Using LESS CSS +AssetsSass:Using Sass +AssetsJSHint:Using JSHint +RequireJS-support:Using RequireJs \ No newline at end of file diff --git a/manual/working/commonGuide/build/AggregatingReverseRouters.md b/manual/working/commonGuide/build/AggregatingReverseRouters.md new file mode 100644 index 00000000..6b677c5e --- /dev/null +++ b/manual/working/commonGuide/build/AggregatingReverseRouters.md @@ -0,0 +1,14 @@ + +# Aggregating reverse routers + +In some situations you want to share reverse routers between sub projects that are not dependent on each other. + +For example, you might have a `web` sub project, and an `api` sub project. These sub projects may have no dependence on each other, except that the `web` project wants to render links to the `api` project (for making AJAX calls), while the `api` project wants to render links to the `web` (rendering the web link for a resource in JSON). In this situation, it would be convenient to use the reverse router, but since these projects don't depend on each other, you can't. + +Play's routes compiler offers a feature that allows a common dependency to generate the reverse routers for projects that depend on it so that the reverse routers can be shared between those projects. This is configured using the `aggregateReverseRoutes` sbt configuration item, like this: + +@[content](code/aggregate.sbt) + +In this setup, the reverse routers for `api` and `web` will be generated as part of the `common` project. Meanwhile, the forwards routers for `api` and `web` will still generate forwards routers, but not reverse routers, because their reverse routers have already been generated in the `common` project which they depend on, so they don't need to generate them. + +> Note that the `common` project has a type of `Project` explicitly declared. This is because there is a recursive reference between it and the `api` and `web` projects, through the `dependsOn` method and `aggregateReverseRoutes` setting, so the Scala type checker needs an explicit type somewhere in the chain of recursion. diff --git a/manual/working/commonGuide/build/Build.md b/manual/working/commonGuide/build/Build.md new file mode 100644 index 00000000..96d36c6d --- /dev/null +++ b/manual/working/commonGuide/build/Build.md @@ -0,0 +1,14 @@ + +# The build system + +This section gives details about Play's build system. + +- [[Overview of the build system|BuildOverview]] +- [[About sbt settings|sbtSettings]] +- [[Manage application dependencies|sbtDependencies]] +- [[Working with sub-projects|sbtSubProjects]] +- [[Play enhancer|PlayEnhancer]] +- [[Aggregating reverse routers|AggregatingReverseRouters]] +- [[Improving Compilation Times|CompilationSpeed]] +- [[Cookbook|sbtCookbook]] +- [[Debugging your build|sbtDebugging]] diff --git a/manual/working/commonGuide/build/BuildOverview.md b/manual/working/commonGuide/build/BuildOverview.md new file mode 100644 index 00000000..fa63e0db --- /dev/null +++ b/manual/working/commonGuide/build/BuildOverview.md @@ -0,0 +1,81 @@ + +# Overview of the build system + +The Play build system uses [sbt](https://www.scala-sbt.org/), a high-performance integrated build for Scala and Java projects. Using `sbt` as our build tool brings certain requirements to play which are explained on this page. + +## Understanding sbt + +sbt functions quite differently to many traditional build tasks. Fundamentally, sbt is a task engine. Your build is represented as a tree of task dependencies that need to be executed, for example, the `compile` task depends on the `sources` task, which depends on the `sourceDirectories` task and the `sourceGenerators` task, and so on. + +sbt breaks typical build executions up into very fine grained tasks, and any task at any point in the tree can be arbitrarily redefined in your build. This makes sbt very powerful, but also requires a shift in thinking if you've come from other build tools that break your build up into very coarsely grained tasks. + +The documentation here describes Play's usage of sbt at a very high level. As you start to use sbt more in your project, it is recommended that you follow the [sbt tutorial](https://www.scala-sbt.org/0.13/tutorial/index.html) to get an understanding for how sbt fits together. Another resource that many people have found useful is [this series of blog posts](https://jazzy.id.au/2015/03/03/sbt-task-engine.html). + +## Play application directory structure + +Most people get started with Play using on of our [example templates](https://playframework.com/download#examples), or with the `sbt new` command, which generally produce a directory structure like this: + +- `/`: The root folder of your application +- `/README`: A text file describing your application that will get deployed with it. +- `/app`: Where your application code will be stored. +- `/build.sbt`: The [sbt](https://www.scala-sbt.org/) settings that describe building your application. +- `/conf`: Configuration files for your application +- `/project`: Further build description information +- `/public`: Where static, public assets for your application are stored. +- `/test`: Where your application's test code will be stored. + +For now, we are going to concern ourselves with the `/build.sbt` file and the `/project` directory. + +> **Tip**: See the complete [[anatomy of a Play application here|Anatomy]]. + +## The `/build.sbt` file. + +An sbt build file for Play generally looks something like this: + +@[default](code/build.sbt) + +The `name` line defines the name of your application and it will be the same as the name of your application's root directory, `/`. In sbt this is derived from the argument that you gave to the `sbt new` command. + +The `version` line provides the version of your application which is used as part of the name for the artifacts your build will produce. + +The `libraryDependencies` line specifies the libraries that your application depends on. You can see more details about [how to manage your dependencies in the sbt docs](https://www.scala-sbt.org/0.13/docs/Library-Management.html). + +Finally, you need to enable an sbt plugin on your project to "Play-ify" it. This adds support for Play-specific features such as the twirl compiler and the routes compiler, and adds the necessary Play libraries to build your project and run the server. Generally you should use one of the following Play plugins for a Play application: + - `PlayScala`: a standard Play Scala project. + - `PlayJava`: a standard Play Java project, with the [[forms|JavaForms]] module. + - `PlayMinimalJava`: a minimal Play Java project, without forms support. + +### Using scala for building + +sbt is also able to construct the build requirements from scala files inside your project's `project` folder. The recommended practice is to use `build.sbt` but there are times when using scala directly is required. If you find yourself, perhaps because you're migrating an older project, then here are a few useful imports: + +```scala +import sbt._ +import Keys._ +import play.sbt._ +import Play.autoImport._ +import PlayKeys._ +``` + +The line indicating `autoImport` is the correct means of importing an sbt plugin's automatically declared properties. Along the same lines, if you're importing an sbt-web plugin then you might well: + +```scala +import com.typesafe.sbt.less.autoImport._ +import LessKeys._ +``` + +## The `/project` directory + +Everything related to building your project is kept in the `/project` directory underneath your application directory. This is an [sbt](https://www.scala-sbt.org/) requirement. Inside that directory, there are two files: + +- `/project/build.properties`: This is a marker file that declares the sbt version used. +- `/project/plugins.sbt`: sbt plugins used by the project build including Play itself. + +## Play plugin for sbt (`/project/plugins.sbt`) + +The Play console and all of its development features like live reloading are implemented via an sbt plugin. It is registered in the `/project/plugins.sbt` file: + +```scala +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % playVersion) // where version is the current Play version, i.e. "%PLAY_VERSION%" +``` +> **Note**: `build.properties` and `plugins.sbt` must be manually updated when you are changing the play version. diff --git a/manual/working/commonGuide/build/CompilationSpeed.md b/manual/working/commonGuide/build/CompilationSpeed.md new file mode 100644 index 00000000..68cf1550 --- /dev/null +++ b/manual/working/commonGuide/build/CompilationSpeed.md @@ -0,0 +1,20 @@ + +# Improving Compilation Times + +Compilation speed can be improved by following some guidelines that are also good engineering practice: + +## Use subprojects/modularize + +This is something like bulkheads for incremental compilation in addition to the other benefits of modularization. It minimizes the size of cycles, makes inter-dependencies explicit, and allows you to work with a subset of the code when desired. It also allows sbt to compile independent modules in parallel. + +## Annotate return types of public methods + +This makes compilation faster as it reduces the need for type inference and for accuracy helps address corner cases in incremental compilation arising from inference across source file boundaries. + +## Avoid large cycles between source files + +Cycles tend to result in larger recompilations and/or more steps. In sbt 0.13.0+ (Play 2.2+), this is less of a problem. + +## Minimize inheritance + +A public API change in a source file typically requires recompiling all descendents. diff --git a/manual/working/commonGuide/build/PlayEnhancer.md b/manual/working/commonGuide/build/PlayEnhancer.md new file mode 100644 index 00000000..1d94335b --- /dev/null +++ b/manual/working/commonGuide/build/PlayEnhancer.md @@ -0,0 +1,52 @@ + +# Play enhancer + +The [Play enhancer](https://github.com/playframework/play-enhancer) is an sbt plugin that generates getters and setters for Java beans, and rewrites the code that accesses those fields to use the getters and setters. + +## Motivation + +One common criticism of Java is that simple things require a lot of boilerplate code. One of the biggest examples of this is encapsulating fields - it is considered good practice to encapsulate the access and mutation of fields in methods, as this allows future changes such as validation and generation of the data. In Java, this means making all your fields private, and then writing getters and setters for each field, a typical overhead of 6 lines of code per field. + +Furthermore, many libraries, particularly libraries that use reflection to access properties of objects such as ORMs, require classes to be implemented in this way, with getters and setters for each field. + +The Play enhancer provides a convenient alternative to manually implementing getters and setters. It implements some post processing on the compiled byte code for your classes, this is commonly referred to as byte code enhancement. For every public field in your classes, Play will automatically generate a getter and setter, and then will rewrite the code that uses these fields to use the getters and setters instead. + +### Drawbacks + +Using byte code enhancement to generating getters and setters is not without its drawbacks however. Here are a few: + +* Byte code enhancement is opaque, you can't see the getters and setters that are generated, so when things go wrong, it can be hard to debug and understand what is happening. Byte code enhancement is ofter described as being "magic" for this reason. +* Byte code enhancement can interfere with the operation of some tooling, such as IDEs, as they will be unaware of the eventual byte code that gets used. This can cause problems such as tests failing when run in an IDE because they depend on byte code enhancement, but the IDE isn't running the byte code enhancer when it compiles your source files. +* Existing Java developers that are new to your codebase will not expect getters and setters to be generated, this can cause confusion. + +Whether you use the Play enhancer or not in your projects is up to you, if you do decide to use it the most important thing is that you understand what the enhancer does, and what the drawbacks may be. + +## Setting up + +To enable the byte code enhancer, simply add the following line to your `project/plugins.sbt` file: + +@[plugins.sbt](code/enhancer.sbt) + +The Play enhancer should be enabled for all your projects. If you want to disable the Play enhancer for a particular project, you can do that like so in your `build.sbt` file: + +@[disable-project](code/enhancer.sbt) + +In some situations, it may not be possible to disable the enhancer plugin, an example of this is using Play's ebean plugin, which requires the enhancer to ensure that getters and setters are generated before it does its byte code enhancement. If you don't want to generate getters and setters in that case, you can use the `playEnhancerEnabled` setting: + +@[disable-enhancement](code/enhancer.sbt) + +## Operation + +The enhancer looks for all fields on Java classes that: + +* are public +* are non static +* are non final + +For each of those fields, it will generate a getter and a setter if they don't already exist. If you wish to provide a custom getter or setter for a field, this can be done by just writing it, the Play enhancer will simply skip the generation of the getter or setter if it already exists. + +## Configuration + +If you want to control exactly which files get byte code enhanced, this can be done by configuring the `sources` task scoped to the `playEnhancerGenerateAccessors` and `playEnhancerRewriteAccessors` tasks. For example, to only enhance the java sources in the models package, you might do this: + +@[select-generate](code/enhancer.sbt) diff --git a/manual/detailedTopics/build/code/aggregate.sbt b/manual/working/commonGuide/build/code/aggregate.sbt similarity index 85% rename from manual/detailedTopics/build/code/aggregate.sbt rename to manual/working/commonGuide/build/code/aggregate.sbt index 29739126..200a1846 100644 --- a/manual/detailedTopics/build/code/aggregate.sbt +++ b/manual/working/commonGuide/build/code/aggregate.sbt @@ -1,3 +1,7 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + //#content lazy val common: Project = (project in file("common")) .enablePlugins(PlayScala) diff --git a/manual/detailedTopics/build/code/build.sbt b/manual/working/commonGuide/build/code/build.sbt similarity index 62% rename from manual/detailedTopics/build/code/build.sbt rename to manual/working/commonGuide/build/code/build.sbt index 8dd14ba5..f9b828c4 100644 --- a/manual/detailedTopics/build/code/build.sbt +++ b/manual/working/commonGuide/build/code/build.sbt @@ -1,3 +1,7 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + //#default name := "foo" @@ -6,8 +10,8 @@ version := "1.0-SNAPSHOT" libraryDependencies ++= Seq( jdbc, anorm, - cache + ehcache ) lazy val root = (project in file(".")).enablePlugins(PlayScala) -//#default \ No newline at end of file +//#default diff --git a/manual/working/commonGuide/build/code/common.build.routes b/manual/working/commonGuide/build/code/common.build.routes new file mode 100644 index 00000000..a351deb6 --- /dev/null +++ b/manual/working/commonGuide/build/code/common.build.routes @@ -0,0 +1,5 @@ +#assets-routes +GET /index controllers.admin.HomeController.index() + +GET /assets/*file controllers.Assets.at(path="/public/lib/myadmin", file) +#assets-routes diff --git a/manual/working/commonGuide/build/code/cookbook.sbt b/manual/working/commonGuide/build/code/cookbook.sbt new file mode 100644 index 00000000..2275618b --- /dev/null +++ b/manual/working/commonGuide/build/code/cookbook.sbt @@ -0,0 +1,25 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + +//#compiler-options +scalacOptions += "-feature" +//#compiler-options + +//#add-assets +unmanagedResourceDirectories in Assets += baseDirectory.value / "pictures" +//#add-assets + +//#disable-scaladoc +sources in (Compile, doc) := Seq.empty +publishArtifact in (Compile, packageDoc) := false +//#disable-scaladoc + +//#ivy-logging +ivyLoggingLevel := UpdateLogging.Quiet +//#ivy-logging + +//#fork-parallel-test +parallelExecution in Test := true +fork in Test := false +//#fork-parallel-test diff --git a/manual/working/commonGuide/build/code/dependencies.sbt b/manual/working/commonGuide/build/code/dependencies.sbt new file mode 100644 index 00000000..e0c5170f --- /dev/null +++ b/manual/working/commonGuide/build/code/dependencies.sbt @@ -0,0 +1,36 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + +//#single-dep +libraryDependencies += "org.apache.derby" % "derby" % "10.13.1.1" +//#single-dep + +//#single-dep-test +libraryDependencies += "org.apache.derby" % "derby" % "10.13.1.1" % "test" +//#single-dep-test + +//#multi-deps +libraryDependencies ++= Seq( + "org.apache.derby" % "derby" % "10.13.1.1", + "org.hibernate" % "hibernate-entitymanager" % "5.2.10.Final" +) +//#multi-deps + +//#explicit-scala-version-dep +libraryDependencies += "org.scala-stm" % "scala-stm_2.11" % "0.8" +//#explicit-scala-version-dep + +//#auto-scala-version-dep +libraryDependencies += "org.scala-stm" %% "scala-stm" % "0.8" +//#auto-scala-version-dep + +//#resolver +resolvers += "sonatype snapshots".at("https://oss.sonatype.org/content/repositories/snapshots/") +//#resolver + +//#local-maven-repos +resolvers += ( + "Local Maven Repository".at(s"file:///${Path.userHome.absolutePath}/.m2/repository") +) +//#local-maven-repos diff --git a/manual/detailedTopics/build/code/enhancer.sbt b/manual/working/commonGuide/build/code/enhancer.sbt similarity index 73% rename from manual/detailedTopics/build/code/enhancer.sbt rename to manual/working/commonGuide/build/code/enhancer.sbt index 4fee31cd..569678ac 100644 --- a/manual/detailedTopics/build/code/enhancer.sbt +++ b/manual/working/commonGuide/build/code/enhancer.sbt @@ -1,5 +1,9 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + //#plugins.sbt -addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % "1.1.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % "1.2.2") //#plugins.sbt //#disable-project diff --git a/manual/working/commonGuide/build/code/javaguide/common/build/controllers/AssetsBuilder.java b/manual/working/commonGuide/build/code/javaguide/common/build/controllers/AssetsBuilder.java new file mode 100644 index 00000000..0c4f6f0d --- /dev/null +++ b/manual/working/commonGuide/build/code/javaguide/common/build/controllers/AssetsBuilder.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// #assets-builder +// ###replace: package controllers.admin; +package javaguide.common.build.controllers; + +import play.api.mvc.*; +import controllers.AssetsMetadata; +import play.api.http.HttpErrorHandler; + +import javax.inject.Inject; + +// ###replace: public class AssetsBuilder extends controllers.Assets { +class Assets extends controllers.Assets { + + @Inject + public Assets(HttpErrorHandler errorHandler, AssetsMetadata meta) { + super(errorHandler, meta); + } + + public Action at(String path, String file) { + boolean aggressiveCaching = true; + return super.at(path, file, aggressiveCaching); + } +} +// #assets-builder diff --git a/manual/working/commonGuide/build/code/javaguide/common/build/controllers/HomeController.java b/manual/working/commonGuide/build/code/javaguide/common/build/controllers/HomeController.java new file mode 100644 index 00000000..b334176d --- /dev/null +++ b/manual/working/commonGuide/build/code/javaguide/common/build/controllers/HomeController.java @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// ###replace: package controllers.admin; +package javaguide.common.build.controllers; + +import play.mvc.Controller; +import play.mvc.Result; + +public class HomeController extends Controller { + public Result index() { + return ok("admin"); + } +} diff --git a/manual/working/commonGuide/build/code/runhook.sbt b/manual/working/commonGuide/build/code/runhook.sbt new file mode 100644 index 00000000..b66171b8 --- /dev/null +++ b/manual/working/commonGuide/build/code/runhook.sbt @@ -0,0 +1,66 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + +// You can't define objects at the root level of an sbt file, so we do it inside a def +def Grunt(base: File) = { + //#grunt-before-started + import play.sbt.PlayRunHook + import sbt._ + import scala.sys.process.Process + + object Grunt { + def apply(base: File): PlayRunHook = { + + object GruntProcess extends PlayRunHook { + + override def beforeStarted(): Unit = { + Process("grunt dist", base).run + } + } + + GruntProcess + } + } + //#grunt-before-started + Grunt(base) +} + +//#grunt-build-sbt +PlayKeys.playRunHooks += Grunt(baseDirectory.value) +//#grunt-build-sbt + +def Grunt2(base: File) = { + //#grunt-watch + import play.sbt.PlayRunHook + import sbt._ + import java.net.InetSocketAddress + import scala.sys.process.Process + + object Grunt { + def apply(base: File): PlayRunHook = { + + object GruntProcess extends PlayRunHook { + + var watchProcess: Option[Process] = None + + override def beforeStarted(): Unit = { + Process("grunt dist", base).run + } + + override def afterStarted(addr: InetSocketAddress): Unit = { + watchProcess = Some(Process("grunt watch", base).run) + } + + override def afterStopped(): Unit = { + watchProcess.map(p => p.destroy()) + watchProcess = None + } + } + + GruntProcess + } + } + //#grunt-watch + Grunt(base) +} diff --git a/manual/working/commonGuide/build/code/scalaguide/common/build/controllers/SubProjectAssets.scala b/manual/working/commonGuide/build/code/scalaguide/common/build/controllers/SubProjectAssets.scala new file mode 100644 index 00000000..f6c44fbe --- /dev/null +++ b/manual/working/commonGuide/build/code/scalaguide/common/build/controllers/SubProjectAssets.scala @@ -0,0 +1,9 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package scalaguide.common.build + +package object controllers { + type AssetsBuilder = _root_.controllers.AssetsBuilder + type Assets = _root_.controllers.Assets +} diff --git a/manual/working/commonGuide/build/code/scalaguide/common/build/controllers/SubProjectsAssetsBuilder.scala b/manual/working/commonGuide/build/code/scalaguide/common/build/controllers/SubProjectsAssetsBuilder.scala new file mode 100644 index 00000000..1ab0b1c7 --- /dev/null +++ b/manual/working/commonGuide/build/code/scalaguide/common/build/controllers/SubProjectsAssetsBuilder.scala @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package common.build.controllers { + + //#assets-builder + import javax.inject._ + + import play.api.http.HttpErrorHandler + + class Assets @Inject()( + errorHandler: HttpErrorHandler, + assetsMetadata: controllers.AssetsMetadata + ) extends controllers.AssetsBuilder(errorHandler, assetsMetadata) + //#assets-builder + + package admin { + //#admin-home-controller + //###insert: package controllers.admin + + import play.api.mvc._ + import javax.inject.Inject + + class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController { + + def index = Action { implicit request => + Ok("admin") + } + } + //#admin-home-controller + } +} diff --git a/manual/detailedTopics/build/images/ivy-report.png b/manual/working/commonGuide/build/images/ivy-report.png similarity index 100% rename from manual/detailedTopics/build/images/ivy-report.png rename to manual/working/commonGuide/build/images/ivy-report.png diff --git a/manual/working/commonGuide/build/index.toc b/manual/working/commonGuide/build/index.toc new file mode 100644 index 00000000..ddbf4739 --- /dev/null +++ b/manual/working/commonGuide/build/index.toc @@ -0,0 +1,10 @@ +Build:Contents +BuildOverview:Overview of the build system +sbtSettings:About sbt settings +sbtDependencies:Manage application dependencies +sbtSubProjects:Working with sub-projects +PlayEnhancer:Play enhancer +AggregatingReverseRouters:Aggregating reverse routers +CompilationSpeed:Improving Compilation Times +sbtCookbook:Cookbook +sbtDebugging:Debugging your build diff --git a/manual/working/commonGuide/build/sbtCookbook.md b/manual/working/commonGuide/build/sbtCookbook.md new file mode 100644 index 00000000..d585e369 --- /dev/null +++ b/manual/working/commonGuide/build/sbtCookbook.md @@ -0,0 +1,72 @@ + +# sbt Cookbook + +## Hooking into Play's dev mode + +When Play runs in dev mode, that is, when using `sbt run`, it's often useful to hook into this to start up additional processes that are required for development. This can be done by defining a `PlayRunHook`, which is a trait with the following methods: + + * `beforeStarted(): Unit` - called before the play application is started, but after all "before run" tasks have been completed. + * `afterStarted(addr: InetSocketAddress): Unit` - called after the play application has been started. + * `afterStopped(): Unit` - called after the play process has been stopped. + +Now let's say you want to build a Web application with `grunt` before the application is started. First, you need to create a Scala object in the `project/` directory to extend `PlayRunHook`. Let's name it `Grunt.scala`: + +@[grunt-before-started](code/runhook.sbt) + +Now you can register this hook in `build.sbt`: + +@[grunt-build-sbt](code/runhook.sbt) + +This will execute the `grunt dist` command in `baseDirectory` before the application is started whenever you run `sbt run`. + +Now we want to modify our run hook to execute the `grunt watch` command to observe changes and rebuild the Web application when they happen, so we'll modify the `Grunt.scala` file we created before: + +@[grunt-watch](code/runhook.sbt) + +Now when the application is started using `sbt run`, `grunt watch` will be executed to rerun the grunt build whenever files change. + +## Add compiler options + +For example, you may want to add the feature flag to have details on feature warnings: + +``` +[info] Compiling 1 Scala source to ~/target/scala-2.10/classes... +[warn] there were 1 feature warnings; re-run with -feature for details +``` + +Simply add `-feature` to the `scalacOptions` attribute: + +@[compiler-options](code/cookbook.sbt) + +## Add additional asset directory + +For example you can add the `pictures` folder to be included as an additional asset directory: + +@[add-assets](code/cookbook.sbt) + +This will allow you to use `routes.Assets.at` with this folder. + +## Disable documentation + +To speed up compilation you can disable documentation generation: + +@[disable-scaladoc](code/cookbook.sbt) + +The first line will disable documentation generation and the second one will avoid to publish the documentation artifact. + +## Configure ivy logging level + +By default `ivyLoggingLevel` is set on `UpdateLogging.DownloadOnly`. You can change this value with: + + * `UpdateLogging.Quiet` only displays errors + * `UpdateLogging.Full` logs the most + +For example if you want to only display errors: + +@[ivy-logging](code/cookbook.sbt) + +## Fork and parallel execution in test + +By default parallel execution is disabled and fork is enabled. You can change this behavior by setting `parallelExecution in Test` and/or `fork in Test`: + +@[fork-parallel-test](code/cookbook.sbt) diff --git a/manual/detailedTopics/build/SBTDebugging.md b/manual/working/commonGuide/build/sbtDebugging.md similarity index 50% rename from manual/detailedTopics/build/SBTDebugging.md rename to manual/working/commonGuide/build/sbtDebugging.md index 9929c5cb..8d083dfc 100644 --- a/manual/detailedTopics/build/SBTDebugging.md +++ b/manual/working/commonGuide/build/sbtDebugging.md @@ -1,55 +1,25 @@ - - # Debugging your build ---> -# ビルドをデバッグする - -sbt を思い通りに動かすのに手こずっているのであれば、ビルドのデバッグを支援するために sbt が提供している組み込みユーティリティをいくつか使う必要があるのかもしれません。 - -## 依存性のデバッグ - -sbt はデフォルトで、どの依存性が他の依存性に取り込まれているかを示す依存性ツリー、そして複数の依存性が要求されたときに sbt がどのようにしてバージョンを選択しているかを表示するコンフリクト解決テーブルを含む、すべての依存性のレポートを生成します。 - -このレポートは、XSL をサポートしているブラウザでは XML から HTML に変換できるようにする XSL スタイルシートを伴った XML ファイルとして生成されます。これをサポートしているブラウザには Firefox と Safari が含まれますが、注目すべきことに Chrome が含まれていません。 - -このレポートはプロジェクトの `target/resolution-cache/reports` ディレクトリ内にプロジェクトのスコープごとに生成されており、例えば `com.example-my-first-app_2.11-compile.xml` のように `organization-projectId_scalaVersion-scope.xml` という名前が付けられています。このレポートは Firefox で開くと以下のように見えます: +The reports can be found in the `target/scala-2.12/resolution-cache/reports/` directory of your project, one is generated for each scope in your project, and are named `organization-projectId_scalaVersion-scope.xml`, for example, `com.example-my-first-app_2.11-compile.xml`. When opened in Firefox, this report looks something like this: [[images/ivy-report.png]] - -## 設定のデバッグ - -sbt は、ビルドを理解し、なにかおかしくなったところを解決するために使うことのできる便利なコマンドをいくつか提供しています。 - -### show コマンド - -show コマンドはあらゆる sbt タスクの戻り値を表示します。例えば、あるソースファイルがコンパイルされるかどうかよく分からない場合、`show sources` を実行して sbt がそれをソースに含めているかどうか見ることができます: ``` [my-first-app] $ show sources @@ -61,15 +31,9 @@ show コマンドはあらゆる sbt タスクの戻り値を表示します。 my-first-app/target/scala-2.11/src_managed/main/controllers/routes.java) ``` - -上記の出力はスクリーンにぴったり収まるように整形されていますが、実行したタスクが長いリストを返す場合は、その内容を理解するためにエディタにコピーする必要があるかもしれません。 - -例えば `test:sources` や `compile:sources` のように、タスクに特定のスコープを指定したり、`my-project/compile:sources` のように特定のプロジェクトを指定することもできます。また、あるタスクが他のタスクのスコープとなっている別の場合は、そのスコープを指定することもできます。例えばプロジェクトの jar ファイルにパッケージされるものをすべて表示するには、`packageBin` タスクをスコープとする `mappings` タスクを表示します: ``` [my-first-app] $ show compile:packageBin::mappings @@ -79,15 +43,9 @@ You can also specify a task a particular scope, eg `test:sources` or `compile:so ... ``` - -### inspect コマンド - -inspect コマンドは、タスクが何に依存しているか、何から依存されているか、どこに定義されているか、などの詳細情報を提供します。これは `show` コマンドと同じように使うことができます: ``` [my-first-app] $ inspect managedSources @@ -105,20 +63,11 @@ inspect コマンドは、タスクが何に依存しているか、何から依 ... ``` - -ここでは `managedSources` コマンドを調査し、このタスクはファイルのシーケンスを提供すること、`Sources generated by the build` という説明文を持つことが分かります。`sourceGenerators` タスクに依存していること、`sources` タスクから依存されていることも確認できます。どこに定義されているかを確認することもできて、この場合は sbt のデフォルトタスク定義の 185 行目です。 - -### inspect tree コマンド - -inspect tree コマンドは、あるタスクのタスクツリー全体を表示します。`unmanagedSources` タスクのタスクツリーを調査した場合、以下のように表示されます: ``` [my-first-app] $ inspect tree unmanagedSources @@ -138,25 +87,13 @@ inspect tree コマンドは、あるタスクのタスクツリー全体を表 [info] +-*/*:excludeFilter = sbt.HiddenFileFilter$@49e479da ``` - -ここでは、どのファイルを含める、あるいは除外するかを判断するフィルタを含め、プロジェクト内にあるソースを発見するために sbt が使うタスクツリー全体が表示されています。`inspect tree` コマンドは、ビルドのある部分がどのように構築されていてのかよく分からず、それらがどのように組み合わさるのかを解明したい場合に特に便利で、これによりさらに詳しく調べていくことができます。 - -## インクリメンタルコンパイルのデバッグ - -人々が Play に持っている共通の問題は、期待していないときに Play が再コンパイルし、再ロードすることです。多くの場合、これはソースジェネレータまたは IDE がなんらかの事情で Play クラスパスの要素を更新し、再ロードを強制することで引き起こされます。このような問題を解決するために、compile タスクのデバッグログを見ることができます。sbt はタスクを実行すると、それを表示するしないに関わらずログ出力をすべてキャプチャするので、必要な場合は後から調査することができます。これは `last` コマンドを使って調査することができます。 - -さて、ここで `compile` を実行して、あるファイルを再コンパイルする必要があったとしましょう: ``` [my-first-app] $ compile @@ -164,10 +101,7 @@ So, let's say you `compile`, and a file needs to be recompiled: [success] Total time: 1 s, completed 07/04/2015 1:28:43 PM ``` - -`last compile` を実行すると、compile コマンドの実行中に起こったことの完全なデバッグログを取得できます。大量のダンプが出力されますが、興味があるのは以下に示す最初の部分だけです: ``` [my-first-app] $ last compile @@ -187,7 +121,4 @@ You can get a full debug log of what happened during the compile command by runn [debug] external source: Set() ``` - -これは、`my-first-app/app/controllers/Application.scala` が変更されたために再コンパイルが引き起こされたことを伝えています。 \ No newline at end of file diff --git a/manual/working/commonGuide/build/sbtDependencies.md b/manual/working/commonGuide/build/sbtDependencies.md new file mode 100644 index 00000000..9e0b0ccb --- /dev/null +++ b/manual/working/commonGuide/build/sbtDependencies.md @@ -0,0 +1,64 @@ + +# Managing library dependencies + +> **Note:** Some sections of this page were copied from the sbt manual, specifically from the [Library Dependencies](https://www.scala-sbt.org/0.13/docs/Library-Dependencies.html) page. You can refer to that page for a more detailed and updated version of the information here. + +## Unmanaged dependencies + +Most people end up using managed dependencies - which allows for fine-grained control, but unmanaged dependencies can be simpler when starting out. + +Unmanaged dependencies work like this: create a `lib/` directory in the root of your project and then add jar files to that directory. They will automatically be added to the application classpath. There's not much else to it. + +There's nothing to add to `build.sbt` to use unmanaged dependencies, although you could change a configuration key if you'd like to use a directory different than `lib`. + +## Managed dependencies + +Play uses [Apache Ivy](http://ant.apache.org/ivy/) (via sbt) to implement managed dependencies, so if you're familiar with Maven or Ivy, you are already used to managed dependencies. + +Most of the time you can simply list your dependencies in the `build.sbt` file. + +Declaring a dependency looks like this (defining `group`, `artifact` and `revision`): + +@[single-dep](code/dependencies.sbt) + +Or like this, with an optional `configuration`: + +@[single-dep-test](code/dependencies.sbt) + +Multiple dependencies can be added either by multiple declarations like the above, or you can provide a Scala sequence: + +@[multi-deps](code/dependencies.sbt) + +Of course, sbt (via Ivy) has to know where to download the module. If your module is in one of the default repositories sbt comes with then this will just work. + +### Getting the right Scala version with `%%` + +If you use `groupID %% artifactID % revision` rather than `groupID % artifactID % revision` (the difference is the double `%%` after the `groupID`), sbt will add your project's Scala version to the artifact name. This is just a shortcut. You could write this without the `%%`: + +@[explicit-scala-version-dep](code/dependencies.sbt) + +Assuming the `scalaVersion` for your build is `2.11.1`, the following is identical (note the double `%%` after `"org.scala-tools"`): + +@[auto-scala-version-dep](code/dependencies.sbt) + +The idea is that many dependencies are compiled for multiple Scala versions, and you'd like to get the one that matches your project to ensure binary compatibility. + +### Resolvers + +sbt uses the standard Maven2 repository and the Typesafe Releases () repositories by default. If your dependency isn't on one of the default repositories, you'll have to add a resolver to help Ivy find it. + +Use the `resolvers` setting key to add your own resolver. For example: + +@[resolver](code/dependencies.sbt) + +sbt can search your local Maven repository if you add it as a repository: + +@[local-maven-repos](code/dependencies.sbt) + +## Handling conflicts between dependencies + +sbt has extensive documentation about how to manage conflict between your dependencies: + +[sbt: Dependencies Conflict Management](https://www.scala-sbt.org/0.13/docs/Library-Management.html#Conflict+Management) + +You can also use [sbt-dependency-graph](https://github.com/jrudolph/sbt-dependency-graph) to have a better visualization of your dependency tree. See also our page about [[debugging sbt|sbtDebugging]] common problems. diff --git a/manual/working/commonGuide/build/sbtSettings.md b/manual/working/commonGuide/build/sbtSettings.md new file mode 100644 index 00000000..6eb211d8 --- /dev/null +++ b/manual/working/commonGuide/build/sbtSettings.md @@ -0,0 +1,20 @@ + +# About sbt Settings + +## About sbt settings + +The `build.sbt` file defines settings for your project. You can also define your own custom settings for your project, as described in the [sbt documentation](https://www.scala-sbt.org). In particular, it helps to be familiar with the [settings](https://www.scala-sbt.org/release/docs/Getting-Started/More-About-Settings) in sbt. + +To set a basic setting, use the `:=` operator: + +```scala +confDirectory := "myConfFolder" +``` + +## Default settings for Java applications + +Play defines a default set of settings suitable for Java-based applications. To enable them add the `PlayJava` plugin via your project's enablePlugins method. These settings mostly define the default imports for generated templates e.g. importing `java.lang.*` so types like `Long` are the Java ones by default instead of the Scala ones. `play.Project.playJavaSettings` also imports `java.util.*` so that the default collection library will be the Java one. + +## Default settings for Scala applications + +Play defines a default set of settings suitable for Scala-based applications. To enable them add the `PlayScala` plugin via your project's enablePlugins method. These default settings define the default imports for generated templates (such as internationalized messages, and core APIs). diff --git a/manual/working/commonGuide/build/sbtSubProjects.md b/manual/working/commonGuide/build/sbtSubProjects.md new file mode 100644 index 00000000..84055358 --- /dev/null +++ b/manual/working/commonGuide/build/sbtSubProjects.md @@ -0,0 +1,248 @@ + +# Working with sub-projects + +A complex project is not necessarily composed of a single Play application. You may want to split a large project into several smaller applications, or even extract some logic into a standard Java or Scala library that has nothing to do with a Play application. + +It will be helpful to read the [sbt documentation on multi-project builds](https://www.scala-sbt.org/release/docs/Getting-Started/Multi-Project). Sub-projects can be fully defined in the parent project's build file, although here we put sub-projects' settings in their own build file. + +## Adding a simple library sub-project + +You can make your application depend on a simple library project. Just add another sbt project definition in your `build.sbt` file: + +``` +name := "my-first-application" + +version := "1.0" + +lazy val myFirstApplication = (project in file(".")) + .enablePlugins(PlayScala) + .aggregate(myLibrary) + .dependsOn(myLibrary) + +lazy val myLibrary = project +``` + +The lowercased `project` on the last line is a Scala Macro which will use the name of the val it is being assigned to in order to determine the project's name and folder. + +The `myFirstApplication` project declares the base project. If you don't have any sub projects, this is already implied, however when declaring sub projects, it's usually required to declare it so that you can ensure that it aggregates (that is, runs things like compile/test etc on the sub projects when run in the base project) and depends on (that is, adds the sub projects to the main projects classpath) the sub projects. + +The above example defines a sub-project in the application’s `myLibrary` folder. This sub-project is a standard sbt project, using the default layout: + +``` +myProject + └ build.sbt + └ app + └ conf + └ public + └ myLibrary + └ build.sbt + └ src + └ main + └ java + └ scala +``` + +`myLibrary` has its own `build.sbt` file, this is where it can declare its own settings, dependencies etc. + +When you have a sub-project enabled in your build, you can focus on this project and compile, test or run it individually. Just use the `projects` command in the Play console prompt to display all projects: + +``` +[my-first-application] $ projects +[info] In file:/Volumes/Data/gbo/myFirstApp/ +[info] * my-first-application +[info] my-library +``` + +The default project is the one whose variable name comes first alphabetically. You may make your main project by making its variable name aaaMain. To change the current project use the `project` command: + +``` +[my-first-application] $ project my-library +[info] Set current project to my-library +> +``` + +When you run your Play application in dev mode, the dependent projects are automatically recompiled, and if something cannot compile you will see the result in your browser: + +[[subprojectError.png]] + +## Sharing common variables and code + +If you want your sub projects and root projects to share some common settings or code, then these can be placed in a Scala file in the `project` directory of the root project. For example, in `project/Common.scala` you might have: + +```scala +import sbt._ +import Keys._ + +object Common { + val settings: Seq[Setting[_]] = Seq( + organization := "com.example", + version := "1.2.3-SNAPSHOT" + ) + + val fooDependency = "com.foo" %% "foo" % "2.4" +} +``` + +Then in each of your `build.sbt` files, you can reference anything declared in the file: + +```scala +name := "my-sub-module" + +Common.settings + +libraryDependencies += Common.fooDependency +``` + +One thing to note is that if you have a mix of Play and non-Play projects, you may need to share Play configuration explicitly. For example, you may want to share the `InjectedRoutesGenerator` and specs2 for every Play project: + +```scala +object Common { + + val playSettings = settings ++ Seq( + routesGenerator := InjectedRoutesGenerator, + libraryDependencies += specs2 % Test, + resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" + ) +} +``` + +And in the sub-project's `build.sbt` file, you would have the following: + +```scala +Common.playSettings +``` + +## Splitting your web application into several parts + +As a Play application is just a standard sbt project with a default configuration, it can depend on another Play application. You can make any sub module a Play application by adding the `PlayJava` or `PlayScala` plugins, depending on whether your project is a Java or Scala project, in its corresponding `build.sbt` file. + +> **Note:** In order to avoid naming collision, make sure your controllers, including the Assets controller in your subprojects are using a different name space than the main project. For example, controllers in the `admin` module should have the fully qualified package name of `admin.MyController`. + +## Splitting the route file + +It's also possible to split the route file into smaller pieces. This is a very handy feature if you want to create a robust, reusable multi-module play application. + +### Consider the following build configuration + +`build.sbt`: + +```scala +name := "myproject" + +lazy val admin = (project in file("modules/admin")).enablePlugins(PlayScala) + +lazy val main = (project in file(".")) + .enablePlugins(PlayScala).dependsOn(admin).aggregate(admin) +``` + +`modules/admin/build.sbt` + +```scala +name := "myadmin" + +libraryDependencies ++= Seq( + "mysql" % "mysql-connector-java" % "5.1.41", + jdbc, + anorm +) +``` + +### Project structure + +``` +build.sbt +app + └ controllers + └ models + └ views +conf + └ application.conf + └ routes +modules + └ admin + └ build.sbt + └ conf + └ admin.routes + └ app + └ controllers + └ models + └ views +project + └ build.properties + └ plugins.sbt +``` + +> **Note:** Configuration and route file names must be unique in the whole project structure. Particularly, there must be only one `application.conf` file and only one `routes` file. To define additional routes or configuration in sub-projects, use sub-project-specific names. For instance, the route file in `admin` is called `admin.routes`. To use a specific set of settings in development mode for a sub project, it would be even better to put these settings into the build file, e.g. `PlayKeys.devSettings += ("play.http.router", "admin.Routes")`. + +`conf/routes`: + +``` +GET /index controllers.HomeController.index() + +-> /admin admin.Routes + +GET /assets/*file controllers.Assets.at(path="/public", file) +``` + +`modules/admin/conf/admin.routes`: + +@[assets-routes](code/common.build.routes) + +> **Note:** Resources are served from a unique classloader, and thus resource path must be relative from project classpath root. +> Subprojects resources are generated in `target/web/public/main/lib/{module-name}`, so the resources are accessible from `/public/lib/{module-name}` when using `play.api.Application#resources(uri)` method, which is what the `Assets.at` method does. + +### Assets and controller classes should be all defined in the `controllers.admin` package + +Java +: @[assets-builder](code/javaguide/common/build/controllers/AssetsBuilder.java) + +Scala +: @[assets-builder](code/scalaguide/common/build/controllers/SubProjectsAssetsBuilder.scala) + +And a controller: + +Java +: @[](code/javaguide/common/build/controllers/HomeController.java) + +Scala +: @[admin-home-controller](code/scalaguide/common/build/controllers/SubProjectsAssetsBuilder.scala) + + +### Reverse routing in ```admin``` + +in case of a regular controller call: + + +``` +controllers.admin.routes.HomeController.index +``` + +and for `Assets`: + +``` +controllers.admin.routes.Assets.at("...") +``` + +### Through the browser + +``` +http://localhost:9000/index +``` + +triggers + +``` +controllers.HomeController.index +``` + +and + +``` +http://localhost:9000/admin/index +``` + +triggers + +``` +controllers.admin.HomeController.index +``` diff --git a/manual/detailedTopics/build/subprojectError.png b/manual/working/commonGuide/build/subprojectError.png similarity index 100% rename from manual/detailedTopics/build/subprojectError.png rename to manual/working/commonGuide/build/subprojectError.png diff --git a/manual/working/commonGuide/configuration/ApplicationSecret.md b/manual/working/commonGuide/configuration/ApplicationSecret.md new file mode 100644 index 00000000..7f19ecd1 --- /dev/null +++ b/manual/working/commonGuide/configuration/ApplicationSecret.md @@ -0,0 +1,74 @@ + +# The Application Secret + +Play uses a secret key for a number of things, including: + +* Signing session cookies and CSRF tokens +* Built in encryption utilities + +It is configured in `application.conf`, with the property name `play.http.secret.key`, and defaults to `changeme`. As the default suggests, it should be changed for production. + +> When started in prod mode, if Play finds that the secret is not set, or if it is set to `changeme`, Play will throw an error. + +## Best practices + +Anyone that can get access to the secret will be able to generate any session they please, effectively allowing them to log in to your system as any user they please. Hence it is strongly recommended that you do not check your application secret into source control. Rather, it should be configured on your production server. This means that it is considered bad practice to put the production application secret in `application.conf`. + +One way of configuring the application secret on a production server is to pass it as a system property to your start script. For example: + +```bash +/path/to/yourapp/bin/yourapp -Dplay.http.secret.key='QCY?tAnfk?aZ?iwrNwnxIlR6CTf:G3gf:90Latabg@5241AB`R5W:1uDFN];Ik@n' +``` + +This approach is very simple, and we will use this approach in the Play documentation on running your app in production mode as a reminder that the application secret needs to be set. In some environments however, placing secrets in command line arguments is not considered good practice. There are two ways to address this. + +### Environment variables + +The first is to place the application secret in an environment variable. In this case, we recommend you place the following configuration in your `application.conf` file: + + play.http.secret.key="changeme" + play.http.secret.key=${?APPLICATION_SECRET} + +The second line in that configuration sets the secret to come from an environment variable called `APPLICATION_SECRET` if such an environment variable is set, otherwise, it leaves the secret unchanged from the previous line. + +This approach works particularly well for cloud based deployment scenarios, where the normal practice is to set passwords and other secrets via environment variables that can be configured through the API for that cloud provider. + +### Production configuration file + +Another approach is to create a `production.conf` file that lives on the server, and includes `application.conf`, but also overrides any sensitive configuration, such as the application secret and passwords. + +For example: + + include "application" + + play.http.secret.key="QCY?tAnfk?aZ?iwrNwnxIlR6CTf:G3gf:90Latabg@5241AB`R5W:1uDFN];Ik@n" + +Then you can start Play with: + +```bash +/path/to/yourapp/bin/yourapp -Dconfig.file=/path/to/production.conf +``` + +## Generating an application secret + +Play provides a utility that you can use to generate a new secret. Run `playGenerateSecret` in the Play console. This will generate a new secret that you can use in your application. For example: + +``` +[my-first-app] $ playGenerateSecret +[info] Generated new secret: QCYtAnfkaZiwrNwnxIlR6CTfG3gf90Latabg5241ABR5W1uDFNIkn +[success] Total time: 0 s, completed 28/03/2014 2:26:09 PM +``` + +## Updating the application secret in application.conf + +Play also provides a convenient utility for updating the secret in `application.conf`, should you want to have a particular secret configured for development or test servers. This is often useful when you have encrypted data using the application secret, and you want to ensure that the same secret is used every time the application is run in dev mode. + +To update the secret in `application.conf`, run `playUpdateSecret` in the Play console: + +``` +[my-first-app] $ playUpdateSecret +[info] Generated new secret: B4FvQWnTp718vr6AHyvdGlrHBGNcvuM4y3jUeRCgXxIwBZIbt +[info] Updating application secret in /Users/jroper/tmp/my-first-app/conf/application.conf +[info] Replacing old application secret: play.http.secret.key="changeme" +[success] Total time: 0 s, completed 28/03/2014 2:36:54 PM +``` diff --git a/manual/working/commonGuide/configuration/ConfigFile.md b/manual/working/commonGuide/configuration/ConfigFile.md new file mode 100644 index 00000000..62be519c --- /dev/null +++ b/manual/working/commonGuide/configuration/ConfigFile.md @@ -0,0 +1,430 @@ + +# Configuration file syntax and features + +> The configuration file used by Play is based on the [Typesafe config library](https://github.com/typesafehub/config). + +The configuration file of a Play application must be defined in `conf/application.conf`. It uses the [HOCON format](https://github.com/typesafehub/config/blob/master/HOCON.md). + +As well as the `application.conf` file, configuration comes from a couple of other places. + +* Default settings are loaded from any `reference.conf` files found on the classpath. Most Play JARs include a `reference.conf` file with default settings. Settings in `application.conf` will override settings in `reference.conf` files. +* It's also possible to set configuration using system properties. System properties override `application.conf` settings. + +The idiomatic way to use Config is to to have all configuration keys defined somewhere, either in `reference.conf` or `application.conf`. If the key does not have a reasonable default value, it is usually set to `null` to signify "no value". + +## Specifying an alternative configuration file + +At runtime, the default `application.conf` is loaded from the classpath. System properties can be used to force a different config source: + +* `config.resource` specifies a resource name including the extension, i.e. `application.conf` and not just `application` +* `config.file` specifies a filesystem path, again it should include the extension, not be a basename + +These system properties specify a replacement for `application.conf`, not an addition. If you still want to use some values from the `application.conf` file then you can include the `application.conf` in your other `.conf` file by writing `include "application"` at the top of that file. After you've included the `application.conf`'s settings in your new `.conf` file you can then specify any settings that you want override. + +## Using from your controller + +The configuration can be available in your controller (or your component), to use the default settings or your custom one, thanks to Dependency Injection (in [[Scala|ScalaDependencyInjection]] or in [[Java|JavaDependencyInjection]]). + +Scala +: @[dependency-injection](code/Configuration.scala) + +Java +: @[dependency-injection](code/javaguide/configuration/MyController.java) + +## Using with Akka + +Akka will use the same configuration file as the one defined for your Play application. Meaning that you can configure anything in Akka in the `application.conf` file. In Play, Akka reads its settings from within the `play.akka` setting, not from the `akka` setting. + +## Using with the `run` command + +There are a couple of special things to know about configuration when running your application with the `run` command. + +### Extra `devSettings` + +You can configure extra settings for the `run` command in your `build.sbt`. These settings won't be used when you deploy your application. + +``` +PlayKeys.devSettings += "play.server.http.port" -> "8080" +``` + +### HTTP server settings in `application.conf` + +In `run` mode the HTTP server part of Play starts before the application has been compiled. This means that the HTTP server cannot access the `application.conf` file when it starts. If you want to override HTTP server settings while using the `run` command you cannot use the `application.conf` file. Instead, you need to either use system properties or the `devSettings` setting shown above. An example of a server setting is the HTTP port. Other server settings can be seen [[here|ProductionConfiguration#Server-configuration-options]]. + +``` +> run -Dhttp.port=1234 +``` + +There is also a specific *namespace* if you need to customize Akka configuration for development mode (the mode used with `run` command). You need to prefix your configuration in `PlayKeys.devSettings` with `play.akka.dev-mode`, for example: + +@[prefix-with-play-akka-dev-mode](code/build.sbt) + +This is specially useful if there is some conflict between the Akka ActorSystem used to run the development server and the ActorSystem used by the application itself. + +## HOCON Syntax + +HOCON has similarities to JSON; you can find the JSON spec at of course. + +### Unchanged from JSON + + - files must be valid UTF-8 + - quoted strings are in the same format as JSON strings + - values have possible types: string, number, object, array, boolean, null + - allowed number formats matches JSON; as in JSON, some possible + floating-point values are not represented, such as `NaN` + +### Comments + +Anything between `//` or `#` and the next newline is considered a comment and ignored, unless the `//` or `#` is inside a quoted string. + +### Omit root braces + +JSON documents must have an array or object at the root. Empty files are invalid documents, as are files containing only a non-array non-object value such as a string. + +In HOCON, if the file does not begin with a square bracket or curly brace, it is parsed as if it were enclosed with `{}` curly braces. + +A HOCON file is invalid if it omits the opening `{` but still has a closing `}`; the curly braces must be balanced. + +### Key-value separator + +The `=` character can be used anywhere JSON allows `:`, i.e. to separate keys from values. + +If a key is followed by `{`, the `:` or `=` may be omitted. So `"foo" {}` means `"foo" : {}"` + +### Commas + +Values in arrays, and fields in objects, need not have a comma between them as long as they have at least one ASCII newline (`\n`, decimal value 10) between them. + +The last element in an array or last field in an object may be followed by a single comma. This extra comma is ignored. + + - `[1,2,3,]` and `[1,2,3]` are the same array. + - `[1\n2\n3]` and `[1,2,3]` are the same array. + - `[1,2,3,,]` is invalid because it has two trailing commas. + - `[,1,2,3]` is invalid because it has an initial comma. + - `[1,,2,3]` is invalid because it has two commas in a row. + - these same comma rules apply to fields in objects. + +### Duplicate keys + +The JSON spec does not clarify how duplicate keys in the same object should be handled. In HOCON, duplicate keys that appear later override those that appear earlier, unless both values are objects. If both values are objects, then the objects are merged. + +Note: this would make HOCON a non-superset of JSON if you assume that JSON requires duplicate keys to have a behavior. The assumption here is that duplicate keys are invalid JSON. + +To merge objects: + + - add fields present in only one of the two objects to the merged object. + - for non-object-valued fields present in both objects, the field found in the second object must be used. + - for object-valued fields present in both objects, the object values should be recursively merged according to these same rules. + +Object merge can be prevented by setting the key to another value first. This is because merging is always done two values at a time; if you set a key to an object, a non-object, then an object, first the non-object falls back to the object (non-object always wins), and then the object falls back to the non-object (no merging, object is the new value). So the two objects never see each other. + +These two are equivalent: + + { + "foo" : { "a" : 42 }, + "foo" : { "b" : 43 } + } + + { + "foo" : { "a" : 42, "b" : 43 } + } + +And these two are equivalent: + + { + "foo" : { "a" : 42 }, + "foo" : null, + "foo" : { "b" : 43 } + } + + { + "foo" : { "b" : 43 } + } + +The intermediate setting of `"foo"` to `null` prevents the object merge. + +### Paths as keys + +If a key is a path expression with multiple elements, it is expanded to create an object for each path element other than the last. The last path element, combined with the value, becomes a field in the most-nested object. + +In other words: + + foo.bar : 42 + +is equivalent to: + + foo { bar : 42 } + +and: + + foo.bar.baz : 42 + +is equivalent to: + + foo { bar { baz : 42 } } + +and so on. These values are merged in the usual way; which implies that: + + a.x : 42, a.y : 43 + +is equivalent to: + + a { x : 42, y : 43 } + +Because path expressions work like value concatenations, you can have whitespace in keys: + + a b c : 42 + +is equivalent to: + + "a b c" : 42 + +Because path expressions are always converted to strings, even single values that would normally have another type become strings. + + - `true : 42` is `"true" : 42` + - `3.14 : 42` is `"3.14" : 42` + +As a special rule, the unquoted string `include` may not begin a path expression in a key, because it has a special interpretation (see below). + +## Substitutions + +Substitutions are a way of referring to other parts of the configuration tree. + +The syntax is `${pathexpression}` or `${?pathexpression}` where the `pathexpression` is a path expression as described above. This path expression has the same syntax that you could use for an object key. + +The `?` in `${?pathexpression}` must not have whitespace before it; the three characters `${?` must be exactly like that, grouped together. + +For substitutions which are not found in the configuration tree, implementations may try to resolve them by looking at system environment variables or other external sources of configuration. (More detail on environment variables in a later section.) + +Substitutions are not parsed inside quoted strings. To get a string containing a substitution, you must use value concatenation with the substitution in the unquoted portion: + + key : ${animal.favorite} is my favorite animal + +Or you could quote the non-substitution portion: + + key : ${animal.favorite}" is my favorite animal" + +Substitutions are resolved by looking up the path in the configuration. The path begins with the root configuration object, i.e. it is "absolute" rather than "relative." + +Substitution processing is performed as the last parsing step, so a substitution can look forward in the configuration. If a configuration consists of multiple files, it may even end up retrieving a value from another file. If a key has been specified more than once, the substitution will always evaluate to its latest-assigned value (the merged object or the last non-object value that was set). + +If a configuration sets a value to `null` then it should not be looked up in the external source. Unfortunately there is no way to "undo" this in a later configuration file; if you have `{ "HOME" : null }` in a root object, then `${HOME}` will never look at the environment variable. There is no equivalent to JavaScript's `delete` operation in other words. + +If a substitution does not match any value present in the configuration and is not resolved by an external source, then it is undefined. An undefined substitution with the `${foo}` syntax is invalid and should generate an error. + +If a substitution with the `${?foo}` syntax is undefined: + + - if it is the value of an object field then the field should not + be created. If the field would have overridden a previously-set + value for the same field, then the previous value remains. + - if it is an array element then the element should not be added. + - if it is part of a value concatenation then it should become an + empty string. + - `foo : ${?bar}` would avoid creating field `foo` if `bar` is + undefined, but `foo : ${?bar} ${?baz}` would be a value + concatenation so if `bar` or `baz` are not defined, the result + is an empty string. + +Substitutions are only allowed in object field values and array elements (value concatenations), they are not allowed in keys or nested inside other substitutions (path expressions). + +A substitution is replaced with any value type (number, object, string, array, true, false, null). If the substitution is the only part of a value, then the type is preserved. Otherwise, it is value-concatenated to form a string. + +Circular substitutions are invalid and should generate an error. + +Implementations must take care, however, to allow objects to refer to paths within themselves. For example, this must work: + + bar : { foo : 42, + baz : ${bar.foo} + } + +Here, if an implementation resolved all substitutions in `bar` as part of resolving the substitution `${bar.foo}`, there would be a cycle. The implementation must only resolve the `foo` field in `bar`, rather than recursing the entire `bar` object. + +## Includes + +### Include syntax + +An _include statement_ consists of the unquoted string `include` and a single quoted string immediately following it. An include statement can appear in place of an object field. + +If the unquoted string `include` appears at the start of a path expression where an object key would be expected, then it is not interpreted as a path expression or a key. + +Instead, the next value must be a _quoted_ string. The quoted string is interpreted as a filename or resource name to be included. + +Together, the unquoted `include` and the quoted string substitute for an object field syntactically, and are separated from the following object fields or includes by the usual comma (and as usual the comma may be omitted if there's a newline). + +If an unquoted `include` at the start of a key is followed by anything other than a single quoted string, it is invalid and an error should be generated. + +There can be any amount of whitespace, including newlines, between the unquoted `include` and the quoted string. + +Value concatenation is NOT performed on the "argument" to `include`. The argument must be a single quoted string. No substitutions are allowed, and the argument may not be an unquoted string or any other kind of value. + +Unquoted `include` has no special meaning if it is not the start of a key's path expression. + +It may appear later in the key: + + # this is valid + { foo include : 42 } + # equivalent to + { "foo include" : 42 } + +It may appear as an object or array value: + + { foo : include } # value is the string "include" + [ include ] # array of one string "include" + +You can quote `"include"` if you want a key that starts with the word `"include"`, only unquoted `include` is special: + + { "include" : 42 } + +### Include semantics: merging + +An _including file_ contains the include statement and an _included file_ is the one specified in the include statement. (They need not be regular files on a filesystem, but assume they are for the moment.) + +An included file must contain an object, not an array. This is significant because both JSON and HOCON allow arrays as root values in a document. + +If an included file contains an array as the root value, it is invalid and an error should be generated. + +The included file should be parsed, producing a root object. The keys from the root object are conceptually substituted for the include statement in the including file. + + - If a key in the included object occurred prior to the include + statement in the including object, the included key's value + overrides or merges with the earlier value, exactly as with + duplicate keys found in a single file. + - If the including file repeats a key from an earlier-included + object, the including file's value would override or merge + with the one from the included file. + +### Include semantics: substitution + +Substitutions in included files are looked up at two different paths; first, relative to the root of the included file; second, relative to the root of the including configuration. + +Recall that substitution happens as a final step, _after_ parsing. It should be done for the entire app's configuration, not for single files in isolation. + +Therefore, if an included file contains substitutions, they must be "fixed up" to be relative to the app's configuration root. + +Say for example that the root configuration is this: + + { a : { include "foo.conf" } } + +And "foo.conf" might look like this: + + { x : 10, y : ${x} } + +If you parsed "foo.conf" in isolation, then `${x}` would evaluate to 10, the value at the path `x`. If you include "foo.conf" in an object at key `a`, however, then it must be fixed up to be `${a.x}` rather than `${x}`. + +Say that the root configuration redefines `a.x`, like this: + + { + a : { include "foo.conf" } + a : { x : 42 } + } + +Then the `${x}` in "foo.conf", which has been fixed up to `${a.x}`, would evaluate to `42` rather than to `10`. Substitution happens _after_ parsing the whole configuration. + +However, there are plenty of cases where the included file might intend to refer to the application's root config. For example, to get a value from a system property or from the reference configuration. So it's not enough to only look up the "fixed up" path, it's necessary to look up the original path as well. + +### Include semantics: missing files + +If an included file does not exist, the include statement should be silently ignored (as if the included file contained only an empty object). + +### Include semantics: locating resources + +Conceptually speaking, the quoted string in an include statement identifies a file or other resource "adjacent to" the one being parsed and of the same type as the one being parsed. The meaning of "adjacent to", and the string itself, has to be specified separately for each kind of resource. + +Implementations may vary in the kinds of resources they support including. + +On the Java Virtual Machine, if an include statement does not identify anything "adjacent to" the including resource, implementations may wish to fall back to a classpath resource. This allows configurations found in files or URLs to access classpath resources. + +For resources located on the Java classpath: + + - included resources are looked up by calling `getResource()` on + the same class loader used to look up the including resource. + - if the included resource name is absolute (starts with '/') + then it should be passed to `getResource()` with the '/' + removed. + - if the included resource name does not start with '/' then it + should have the "directory" of the including resource. + prepended to it, before passing it to `getResource()`. If the + including resource is not absolute (no '/') and has no "parent + directory" (is just a single path element), then the included + relative resource name should be left as-is. + - it would be wrong to use `getResource()` to get a URL and then + locate the included name relative to that URL, because a class + loader is not required to have a one-to-one mapping between + paths in its URLs and the paths it handles in `getResource()`. + In other words, the "adjacent to" computation should be done + on the resource name not on the resource's URL. + +For plain files on the filesystem: + + - if the included file is an absolute path then it should be kept + absolute and loaded as such. + - if the included file is a relative path, then it should be + located relative to the directory containing the including + file. The current working directory of the process parsing a + file must NOT be used when interpreting included paths. + - if the file is not found, fall back to the classpath resource. + The classpath resource should not have any package name added + in front, it should be relative to the "root"; which means any + leading "/" should just be removed (absolute is the same as + relative since it's root-relative). The "/" is handled for + consistency with including resources from inside other + classpath resources, where the resource name may not be + root-relative and "/" allows specifying relative to root. + +URLs: + + - for both filesystem files and Java resources, if the + included name is a URL (begins with a protocol), it would + be reasonable behavior to try to load the URL rather than + treating the name as a filename or resource name. + - for files loaded from a URL, "adjacent to" should be based + on parsing the URL's path component, replacing the last + path element with the included name. + - file: URLs should behave in exactly the same way as a plain + filename + +## Duration format + +The supported unit strings for duration are case sensitive and must be lowercase. Exactly these strings are supported: + + - `ns`, `nanosecond`, `nanoseconds` + - `us`, `microsecond`, `microseconds` + - `ms`, `millisecond`, `milliseconds` + - `s`, `second`, `seconds` + - `m`, `minute`, `minutes` + - `h`, `hour`, `hours` + - `d`, `day`, `days` + +## Size in bytes format + +For single bytes, exactly these strings are supported: + + - `B`, `b`, `byte`, `bytes` + +For powers of ten, exactly these strings are supported: + + - `kB`, `kilobyte`, `kilobytes` + - `MB`, `megabyte`, `megabytes` + - `GB`, `gigabyte`, `gigabytes` + - `TB`, `terabyte`, `terabytes` + - `PB`, `petabyte`, `petabytes` + - `EB`, `exabyte`, `exabytes` + - `ZB`, `zettabyte`, `zettabytes` + - `YB`, `yottabyte`, `yottabytes` + +For powers of two, exactly these strings are supported: + + - `K`, `k`, `Ki`, `KiB`, `kibibyte`, `kibibytes` + - `M`, `m`, `Mi`, `MiB`, `mebibyte`, `mebibytes` + - `G`, `g`, `Gi`, `GiB`, `gibibyte`, `gibibytes` + - `T`, `t`, `Ti`, `TiB`, `tebibyte`, `tebibytes` + - `P`, `p`, `Pi`, `PiB`, `pebibyte`, `pebibytes` + - `E`, `e`, `Ei`, `EiB`, `exbibyte`, `exbibytes` + - `Z`, `z`, `Zi`, `ZiB`, `zebibyte`, `zebibytes` + - `Y`, `y`, `Yi`, `YiB`, `yobibyte`, `yobibytes` + +## Conventional override by system properties + +Java system properties override settings found in the `application.conf` and `reference.conf` files. This supports specifying config options on the command line, for example `sbt -Dkey=value run`. + +> **Note**: Play forks the JVM for tests - and so to use command line overrides in tests you must add `Keys.fork in Test := false` in `build.sbt` before you can use them for a test. diff --git a/manual/working/commonGuide/configuration/Configuration.md b/manual/working/commonGuide/configuration/Configuration.md new file mode 100644 index 00000000..fb37b6f8 --- /dev/null +++ b/manual/working/commonGuide/configuration/Configuration.md @@ -0,0 +1,15 @@ + +# Configuration + +This section explains how to configure your Play application. + +- [[Configuration file syntax and features|ConfigFile]] +- [[Configuring the application secret|ApplicationSecret]] +- [[Configuring the session cookie|SettingsSession]] +- [[Configuring the JDBC connection pool|SettingsJDBC]] +- [[Configuring Play's thread pools|ThreadPools]] +- [[Configuring Akka Http Server Backend|SettingsAkkaHttp]] +- [[Configuring Netty Server Backend|SettingsNetty]] +- [[Configuring logging|SettingsLogger]] +- [[Configuring WS SSL|WsSSL]] +- [[Configuring WS Cache|WsCache]] diff --git a/manual/working/commonGuide/configuration/SettingsAkkaHttp.md b/manual/working/commonGuide/configuration/SettingsAkkaHttp.md new file mode 100644 index 00000000..5706b1f4 --- /dev/null +++ b/manual/working/commonGuide/configuration/SettingsAkkaHttp.md @@ -0,0 +1,51 @@ + +# Configuring the Akka HTTP server backend + +By default, Play uses the [[Akka HTTP server backend|AkkaHttpServer]]. + +Like the rest of Play, the Akka HTTP server backend is configured with Typesafe Config. + +@[](/confs/play-akka-http-server/reference.conf) + +The configurations above are specific to Akka HTTP server backend, but other more generic configurations are also available: + +@[](/confs/play-server/reference.conf) + +You can read more about the configuration settings in the [Akka HTTP documentation](https://doc.akka.io/docs/akka-http/current/configuration.html?language=scala). + +> **Note:** Akka HTTP has a number of [timeouts configurations](https://doc.akka.io/docs/akka-http/current/common/timeouts.html?language=scala#server-timeouts) that you can use to protect your application from attacks or programming mistakes. The Akka HTTP Server in Play will automatically recognize all these Akka configurations. For example, if you have `idle-timeout` and `request-timeout` configurations like below: +> +> ``` +> akka.http.server.idle-timeout = 20s +> akka.http.server.request-timeout = 30s +> ``` +> +> They will be automatically recognized. Keep in mind that Play configurations listed above will override the Akka ones. + +There is also a separate configuration file for the HTTP/2 support in Akka HTTP, if you have [[enabled the `AkkaHttp2Support` plugin|AkkaHttpServer#HTTP/2-support-(experimental)]]: + +@[](/confs/play-akka-http2-support/reference.conf) + +> **Note:** In dev mode, when you use the `run` command, your `application.conf` settings will not be picked up by the server. This is because in dev mode the server starts before the application classpath is available. There are several [[other options|ConfigFile#Using-with-the-run-command]] you'll need to use instead. + +## Direct Akka HTTP configuration + +If you need direct access to Akka HTTP's `ServerSettings` and `ParserSettings` objects you can do this by extending Play's `AkkaHttpServer` class with your own. The `AkkaHttpServer` class has several protected methods which can be overridden to change how Play configures its Akka HTTP backend. + +Note that writing your own server class is advanced usage. Usually you can do all the configuration you need through normal configuration settings. + +The code below shows an example of a custom server which modifies some Akka HTTP settings. Below the server class is a `ServerProvider` class which acts as a factory for the custom server. + +@[custom-akka-http-server](code/CustomAkkaHttpServer.scala) + +Once you've written a custom server and `ServerProvider` class you'll need to tell Play about them by setting the `play.server.provider` configuration option to the full name of your `ServerProvider` class. + +For example, adding the following settings to your `build.sbt` and `application.conf` will tell Play to use your new server for both the sbt `run` task and when your application is deployed. + +`build.sbt`: + +@[custom-akka-http-server-provider](code/build.sbt) + +`application.conf`: + +@[custom-akka-http-server-provider](code/application.conf) \ No newline at end of file diff --git a/manual/working/commonGuide/configuration/SettingsJDBC.md b/manual/working/commonGuide/configuration/SettingsJDBC.md new file mode 100644 index 00000000..d37602a7 --- /dev/null +++ b/manual/working/commonGuide/configuration/SettingsJDBC.md @@ -0,0 +1,39 @@ + +# Configuring the JDBC pool. + +The Play JDBC datasource is managed by [HikariCP](https://brettwooldridge.github.io/HikariCP/). + +## Special URLs + +Play supports special url format for both **MySQL** and **PostgreSQL**: + +``` +# To configure MySQL +db.default.url="mysql://user:password@localhost/database" + +# To configure PostgreSQL +db.default.url="postgres://user:password@localhost/database" +``` + +A non-standard port of the database service can be specified: + +``` +# To configure MySQL running in Docker +db.default.url="mysql://user:password@localhost:port/database" + +# To configure PostgreSQL running in Docker +db.default.url="postgres://user:password@localhost:port/database" +``` + +## Reference + +In addition to the classical `driver`, `url`, `username`, `password` configuration properties, it also supports additional tuning parameters if you need them. The `play.db.prototype` configuration from the Play JDBC `reference.conf` is used as the prototype for the configuration for all database connections. The defaults for all the available configuration options can be seen here: + +@[](/confs/play-jdbc/reference.conf) + +When you need to specify some settings for a connection pool, you can override the prototype settings. For example, to set the default connection pool for BoneCP and set maxConnectionsPerPartition for the default pool, you would set the following in your `application.conf` file: + +``` +play.db.pool=bonecp +play.db.prototype.bonecp.maxConnectionsPerPartition = 50 +``` diff --git a/manual/working/commonGuide/configuration/SettingsLogger.md b/manual/working/commonGuide/configuration/SettingsLogger.md new file mode 100644 index 00000000..9cacdc0d --- /dev/null +++ b/manual/working/commonGuide/configuration/SettingsLogger.md @@ -0,0 +1,240 @@ + +# Configuring logging + +Play uses SLF4J for logging, backed by [Logback](https://logback.qos.ch/) as its default logging engine. See the [Logback documentation](https://logback.qos.ch/manual/configuration.html) for details on configuration. + +## Default configuration + +In dev mode Play uses the following default configuration: + +@[](/confs/play-logback/logback-play-dev.xml) + +Play uses the following default configuration in production: + +@[](/confs/play-logback/logback-play-default.xml) + +A few things to note about these configurations: + +* These default configs specify only a console logger which outputs only 10 lines of an exception stack trace. +* Play uses ANSI color codes by default in level messages. +* For production, the default config puts the console logger behind the logback [AsyncAppender](https://logback.qos.ch/manual/appenders.html#AsyncAppender). For details on the performance implications on this, see this [blog post](https://blog.takipi.com/how-to-instantly-improve-your-java-logging-with-7-logback-tweaks/). +* In order to guarantee that logged messages have had a chance to be processed by asynchronous appenders (including the TCP appender) and ensure background threads have been stopped, you'll need to cleanly shut down logback when your application exits. For details on a shutdown hook, see this [documentation](https://logback.qos.ch/manual/configuration.html#shutdownHook). Also [you must specify](https://jira.qos.ch/browse/LOGBACK-1090) DelayingShutdownHook explicitly: `` . + +To add a file logger, add the following appender to your `conf/logback.xml` file: + +```xml + + ${application.home:-.}/logs/application.log + + %date [%level] from %logger in %thread - %message%n%xException + + +``` + +Optionally use the async appender to wrap the `FileAppender`: +```xml + + + +``` + +Add the necessary appender(s) to the root: +```xml + + + + +``` + +## Security Logging + +A security marker has been added for security related operations in Play, and failed security checks now log at WARN level, with the security marker set. This ensures that developers always know why a particular request is failing, which is important now that security filters are enabled by default in Play. + +The security marker also allows security failures to be triggered or filtered distinct from normal logging. For example, to disable all logging with the SECURITY marker set, add the following lines to the `logback.xml` file: + +```xml + + SECURITY + DENY + +``` + +In addition, log events using the security marker can also trigger a message to a Security Information & Event Management (SEIM) engine for further processing. + +## Using a custom application loader + +Note that when using a custom application loader that does not extend the default `GuiceApplicationLoader` (for example when using [[compile-time dependency injection|ScalaCompileTimeDependencyInjection]]), the `LoggerConfigurator` needs to be manually invoked to pick up your custom configuration. You can do this with code like the following: + +@[basicextended](../../scalaGuide/main/dependencyinjection/code/CompileTimeDependencyInjection.scala) + +## Custom configuration + +For any custom configuration, you will need to specify your own Logback configuration file. + +### Using a configuration file from project source + +You can provide a default logging configuration by providing a file `conf/logback.xml`. + +### Using an external configuration file + +You can also specify a configuration file via a System property. This is particularly useful for production environments where the configuration file may be managed outside of your application source. + +> **Note**: The logging system gives top preference to configuration files specified by system properties, secondly to files in the `conf` directory, and lastly to the default. This allows you to customize your application's logging configuration and still override it for specific environments or developer setups. + +#### Using `-Dlogger.resource` + +Specify a configuration file to be loaded from the classpath: + +``` +$ start -Dlogger.resource=prod-logger.xml +``` + +#### Using `-Dlogger.file` + +Specify a configuration file to be loaded from the file system: + +``` +$ start -Dlogger.file=/opt/prod/logger.xml +``` + +> **Note**: To see which file is being used, you can set a system property to debug it: `-Dlogback.debug=true`. + +### Examples + +Here's an example of configuration that uses a rolling file appender, as well as a separate appender for outputting an access log: + +```xml + + + + ${application.home:-.}/logs/application.log + + + ${application.home:-.}/logs/application-log-%d{yyyy-MM-dd}.gz + + 30 + + + %date{yyyy-MM-dd HH:mm:ss ZZZZ} [%level] from %logger in %thread - %message%n%xException + + + + + + + SECURITY + + DENY + ACCEPT + + ${application.home:-.}/logs/security.log + + %date [%level] [%marker] from %logger in %thread - %message%n%xException + + + + + ${application.home:-.}/logs/access.log + + + ${application.home:-.}/logs/access-log-%d{yyyy-MM-dd}.gz + + 7 + + + %date{yyyy-MM-dd HH:mm:ss ZZZZ} %message%n + + false + + + + + + + + + + + + + + +``` + +This demonstrates a few useful features: + +- It uses `RollingFileAppender` which can help manage growing log files. See more [details here](https://logback.qos.ch/manual/appenders.html#SizeAndTimeBasedRollingPolicy). +- It writes log files to a directory external to the application so they will not affected by upgrades, etc. +- The `FILE` appender uses an expanded message format that can be parsed by third party log analytics providers such as Sumo Logic. +- The `access` logger is routed to a separate log file using the `ACCESS_FILE` appender. +- Any log messages sent with the "SECURITY" marker attached are logged to the `security.log` file using the [EvaluatorFilter](https://logback.qos.ch/manual/filters.html#evalutatorFilter) and the [OnMarkerEvaluator](https://logback.qos.ch/manual/appenders.html#OnMarkerEvaluator). +- All loggers are set to a threshold of `INFO` which is a common choice for production logging. + +> **Note**: the `file` tag is optional and you can omit it if you want to avoid file renaming. See [Logback docs](https://logback.qos.ch/codes.html#renamingError) for more information. + +## Including Properties + +By default, only the property `application.home` is exported to the logging framework, meaning that files can be referenced relative to the Play application: + +``` + ${application.home:-}/example.log +``` + +If you want to reference properties that are defined in the `application.conf` file, you can add `play.logger.includeConfigProperties=true` to your application.conf file. When the application starts, all properties defined in configuration will be available to the logger: + +``` + + + context = ${my.property.defined.in.application.conf} %message%n + + +``` + +## Akka logging configuration + +Akka system logging can be done by changing the `akka` logger to INFO. + +```xml + + + + +``` + +You may also wish to configure an appender for the Akka loggers that includes useful properties such as thread and actor address. For more information about configuring Akka's logging, including details on Logback and Slf4j integration, see the [Akka documentation](https://doc.akka.io/docs/akka/2.5/logging.html?language=scala). + +## Using a Custom Logging Framework + +Play uses Logback by default, but it is possible to configure Play to use another logging framework as long as there is an SLF4J adapter for it. To do this, the `PlayLogback` sbt plugin must be disabled using `disablePlugins`: + +```scala +lazy val root = (project in file(".")) + .enablePlugins(PlayScala) + .disablePlugins(PlayLogback) +``` + +From there, a custom logging framework can be used. Here, Log4J 2 is used as an example. + +```scala +libraryDependencies ++= Seq( + "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.4.1", + "org.apache.logging.log4j" % "log4j-api" % "2.4.1", + "org.apache.logging.log4j" % "log4j-core" % "2.4.1" +) +``` + +Once the libraries and the SLF4J adapter are loaded, the `log4j.configurationFile` system property can be set on the command line as usual. + +If custom configuration depending on Play's mode is required, you can do additional customization with the `LoggerConfigurator`. To do this, add a `logger-configurator.properties` to the classpath, with + +```properties +play.logger.configurator=Log4J2LoggerConfigurator +``` + +And then extend LoggerConfigurator with any customizations: + +Java +: @[log4j2-class](code/JavaLog4JLoggerConfigurator.java) + +Scala +: @[log4j2-class](code/Log4j2LoggerConfigurator.scala) diff --git a/manual/working/commonGuide/configuration/SettingsNetty.md b/manual/working/commonGuide/configuration/SettingsNetty.md new file mode 100644 index 00000000..f229e80c --- /dev/null +++ b/manual/working/commonGuide/configuration/SettingsNetty.md @@ -0,0 +1,32 @@ + +# Configuring Netty Server Backend + +The Netty server backend is built on top of [Netty](https://netty.io/). + +> **Note**: The Netty server backend is not the default in 2.6.x, and so must be specifically enabled. See more information in [[Netty Server|NettyServer]] documentation. + +## Default configuration + +Play uses the following default configuration: + +@[](/confs/play-netty-server/reference.conf) + +The configurations above are specific to Netty server backend, but other more generic configurations are also available: + +@[](/confs/play-server/reference.conf) + +## Configuring transport socket + +Native socket transport has higher performance and produces less garbage but is only available on Linux. You can configure the transport socket type in `application.conf`: + +```properties +play.server { + netty { + transport = "native" + } +} +``` + +## Configuring channel options + +The available options are defined in [Netty channel option documentation](https://netty.io/4.1/api/io/netty/channel/ChannelOption.html). If you are using native socket transport you can set [additional options](https://netty.io/4.1/api/io/netty/channel/epoll/EpollChannelOption.html). diff --git a/manual/working/commonGuide/configuration/SettingsSession.md b/manual/working/commonGuide/configuration/SettingsSession.md new file mode 100644 index 00000000..192ab143 --- /dev/null +++ b/manual/working/commonGuide/configuration/SettingsSession.md @@ -0,0 +1,31 @@ + +# Configuring the session cookie + +Play stores the session using a session cookie in the browser. When you are programming, you will typically access the session through the [[Scala API|ScalaSessionFlash]] or [[Java API|JavaSessionFlash]], but there are useful configuration settings. + +Session and flash cookies are stored in [JSON Web Token](https://tools.ietf.org/html/rfc7519) (JWT) format. The encoding is transparent to Play, but there some useful properties of JWT which can be leveraged for session cookies, and can be configured through `application.conf`. Note that JWT is typically used in an HTTP header value, which is not what is active here -- in addition, the JWT is signed using the secret, but is not encrypted by Play. + +## Not Before Support + +When a session cookie is created, the "issued at" `iat` and "not before" `nbf` claims in JWT will be set to the time of cookie creation, which prevents a cookie from being accepted before the current time. + +## Session Timeout / Expiration + +By default, there is no technical timeout for the Session. It expires when the user closes the web browser. If you need a functional timeout for a specific application, you set the maximum age of the session cookie by configuring the key `play.http.session.maxAge` in `application.conf`, and this will also set `play.http.session.jwt.expiresAfter` to the same value. The `maxAge` property will remove the cookie from the browser, and the JWT `exp` claim will be set in the cookie, and will make it invalid after the given duration. + +## URL Encoded Cookie Encoding + +The session cookie uses the JWT cookie encoding. If you want, you can revert back to URL encoded cookie encoding by switching to `play.api.mvc.LegacyCookiesModule` in the application.conf file: + +``` +play.modules.disabled+="play.api.mvc.CookiesModule" +play.modules.enabled+="play.api.mvc.LegacyCookiesModule" +``` + +## Session Configuration + +The default session configuration is as follows: + +@[session-configuration](/confs/play/reference.conf) + + diff --git a/manual/working/commonGuide/configuration/ThreadPools.md b/manual/working/commonGuide/configuration/ThreadPools.md new file mode 100644 index 00000000..fcdfb3e5 --- /dev/null +++ b/manual/working/commonGuide/configuration/ThreadPools.md @@ -0,0 +1,192 @@ + +# Understanding Play thread pools + +Play Framework is, from the bottom up, an asynchronous web framework. Streams are handled asynchronously using iteratees. Thread pools in Play are tuned to use fewer threads than in traditional web frameworks, since IO in play-core never blocks. + +Because of this, if you plan to write blocking IO code, or code that could potentially do a lot of CPU intensive work, you need to know exactly which thread pool is bearing that workload, and you need to tune it accordingly. Doing blocking IO without taking this into account is likely to result in very poor performance from Play Framework, for example, you may see only a few requests per second being handled, while CPU usage sits at 5%. In comparison, benchmarks on typical development hardware (eg, a MacBook Pro) have shown Play to be able to handle workloads in the hundreds or even thousands of requests per second without a sweat when tuned correctly. + +## Knowing when you are blocking + +The most common place where a typical Play application will block is when it's talking to a database. Unfortunately, none of the major databases provide asynchronous database drivers for the JVM, so for most databases, your only option is to using blocking IO. A notable exception to this is [ReactiveMongo](http://reactivemongo.org/), a driver for MongoDB that uses Play's Iteratee library to talk to MongoDB. + +Other cases when your code may block include: + +* Using REST/WebService APIs through a 3rd party client library (ie, not using Play's asynchronous WS API) +* Some messaging technologies only provide synchronous APIs to send messages +* When you open files or sockets directly yourself +* CPU intensive operations that block by virtue of the fact that they take a long time to execute + +In general, if the API you are using returns `Future`s, it is non-blocking, otherwise it is blocking. + +> Note that you may be tempted to therefore wrap your blocking code in Futures. This does not make it non-blocking, it just means the blocking will happen in a different thread. You still need to make sure that the thread pool that you are using has enough threads to handle the blocking. Please see Play's example templates on https://playframework.com/download#examples for how to configure your application for a blocking API. + +In contrast, the following types of IO do not block: + +* The Play WS API +* Asynchronous database drivers such as ReactiveMongo +* Sending/receiving messages to/from Akka actors + +## Play's thread pools + +Play uses a number of different thread pools for different purposes: + +* **Internal thread pools** - These are used internally by the server engine for handling IO. An application's code should never be executed by a thread in these thread pools. Play is configured with Akka HTTP server backend by default, and so [[configuration settings|SettingsAkkaHttp]] from `application.conf` should be used to change the backend. Alternately, Play also comes with a Netty server backend which, if enabled, also has settings that can be [[configured|SettingsNetty]] from `application.conf`. + +* **Play default thread pool** - This is the thread pool in which all of your application code in Play Framework is executed. It is an Akka dispatcher, and is used by the application `ActorSystem`. It can be configured by configuring Akka, described below. + +## Using the default thread pool + +All actions in Play Framework use the default thread pool. When doing certain asynchronous operations, for example, calling `map` or `flatMap` on a future, you may need to provide an implicit execution context to execute the given functions in. An execution context is basically another name for a `ThreadPool`. + +In most situations, the appropriate execution context to use will be the **Play default thread pool**. This is accessible through `@Inject()(implicit ec: ExecutionContext)` This can be used by injecting it into your Scala source file: + +@[global-thread-pool](code/ThreadPools.scala) + +or using [`CompletionStage`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html) with an [`HttpExecutionContext`](api/java/play/libs/concurrent/HttpExecutionContext.html) in Java code: + +@[http-execution-context](code/detailedtopics/httpec/MyController.java) + +This execution context connects directly to the Application's `ActorSystem` and uses the [default dispatcher](https://doc.akka.io/docs/akka/2.5/dispatchers.html?language=scala). + +### Configuring the default thread pool + +The default thread pool can be configured using standard Akka configuration in `application.conf` under the `akka` namespace. Here is default configuration for Play's thread pool: + +@[default-config](code/ThreadPools.scala) + +This configuration instructs Akka to create 1 thread per available processor, with a maximum of 24 threads in the pool. + +You can also try the default Akka configuration: + +@[akka-default-config](code/ThreadPools.scala) + +The full configuration options available to you can be found [here](https://doc.akka.io/docs/akka/2.5.3/java/general/configuration.html#listing-of-the-reference-configuration). + +## Using other thread pools + +In certain circumstances, you may wish to dispatch work to other thread pools. This may include CPU heavy work, or IO work, such as database access. To do this, you should first create a `ThreadPool`, this can be done easily in Scala: + +@[my-context-usage](code/ThreadPools.scala) + +In this case, we are using Akka to create the `ExecutionContext`, but you could also easily create your own `ExecutionContext`s using Java executors, or the Scala fork join thread pool, for example. Play provides `play.libs.concurrent.CustomExecutionContext` and `play.api.libs.concurrent.CustomExecutionContext` that can be used to create your own execution contexts. Please see [[ScalaAsync]] or [[JavaAsync]] for further details. + +To configure this Akka execution context, you can add the following configuration to your `application.conf`: + +@[my-context-config](code/ThreadPools.scala) + +To use this execution context in Scala, you would simply use the scala `Future` companion object function: + +@[my-context-explicit](code/ThreadPools.scala) + +or you could just use it implicitly: + +@[my-context-implicit](code/ThreadPools.scala) + +In addition, please see the example templates on https://playframework.com/download#examples for examples of how to configure your application for a blocking API. + +## Class loaders and thread locals + +Class loaders and thread locals need special handling in a multithreaded environment such as a Play program. + +### Application class loader + +In a Play application the [thread context class loader](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#getContextClassLoader--) may not always be able to load application classes. You should explicitly use the application class loader to load classes. + +Java +: @[using-app-classloader](code/detailedtopics/ThreadPoolsJava.java) + +Scala +: @[using-app-classloader](code/ThreadPools.scala) + +Being explicit about loading classes is most important when running Play in development mode (using `run`) rather than production mode. That's because Play's development mode uses multiple class loaders so that it can support automatic application reloading. Some of Play's threads might be bound to a class loader that only knows about a subset of your application's classes. + +In some cases you may not be able to explicitly use the application classloader. This is sometimes the case when using third party libraries. In this case you may need to set the [thread context class loader](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#getContextClassLoader--) explicitly before you call the third party code. If you do, remember to restore the context class loader back to its previous value once you've finished calling the third party code. + +### Java thread locals + +Java code in Play uses a `ThreadLocal` to find out about contextual information such as the current HTTP request. Scala code doesn't need to use `ThreadLocal`s because it can use implicit parameters to pass context instead. `ThreadLocal`s are used in Java so that Java code can access contextual information without needing to pass context parameters everywhere. + +The problem with using thread locals however is that as soon as control switches to another thread, you lose thread local information. So if you were to map a `CompletionStage` using `thenApplyAsync`, or using `thenApply` at a point in time after the `Future` associated with that `CompletionStage` had completed, and you then try to access the HTTP context (eg, the session or request), it won't work . To address this, Play provides an [`HttpExecutionContext`](api/java/play/libs/concurrent/HttpExecutionContext.html). This allows you to capture the current context in an `Executor`, which you can then pass to the `CompletionStage` `*Async` methods such as `thenApplyAsync()`, and when the executor executes your callback, it will ensure the thread local context is setup so that you can access the request/session/flash/response objects. + +To use the `HttpExecutionContext`, inject it into your component, and then pass the current context anytime a `CompletionStage` is interacted with. For example: + +@[http-execution-context](code/detailedtopics/httpec/MyController.java) + +If you have a custom executor, you can wrap it in an `HttpExecutionContext` simply by passing it to the `HttpExecutionContext`s constructor. + +## Best practices + +How you should best divide work in your application between different thread pools greatly depends on the types of work that your application is doing, and the control you want to have over how much of which work can be done in parallel. There is no one size fits all solution to the problem, and the best decision for you will come from understanding the blocking-IO requirements of your application and the implications they have on your thread pools. It may help to do load testing on your application to tune and verify your configuration. + +> **Note:** In a blocking environment, `thread-pool-executor` is better than `fork-join` because no work-stealing is possible, and a `fixed-pool-size` size should be used and set to the maximum size of the underlying resource. +> +> Given the fact that JDBC is blocking, thread pools can be sized to the number of connections available to a database pool, assuming that the thread pool is used exclusively for database access. Fewer threads will not consume the number of connections available. Any more threads than the number of connections available could be wasteful given contention for the connections. + +Below we outline a few common profiles that people may want to use in Play Framework: + +### Pure asynchronous + +In this case, you are doing no blocking IO in your application. Since you are never blocking, the default configuration of one thread per processor suits your use case perfectly, so no extra configuration needs to be done. The Play default execution context can be used in all cases. + +### Highly synchronous + +This profile matches that of a traditional synchronous IO based web framework, such as a Java servlet container. It uses large thread pools to handle blocking IO. It is useful for applications where most actions are doing database synchronous IO calls, such as accessing a database, and you don't want or need control over concurrency for different types of work. This profile is the simplest for handling blocking IO. + +In this profile, you would use the default execution context everywhere, but configure it to have a very large number of threads in its pool. Because the default thread pool is used for both servicing Play requests and database requests, the fixed pool size should be the maximum size of database connection pool, plus the number of cores, plus a couple extra for housekeeping, like so: + +@[highly-synchronous](code/ThreadPools.scala) + +This profile is recommended for Java applications that do synchronous IO, since it is harder in Java to dispatch work to other threads. + +In addition, please see the example templates on https://playframework.com/download#examples for examples of how to configure your application for a blocking API. + +### Many specific thread pools + +This profile is for when you want to do a lot of synchronous IO, but you also want to control exactly how much of which types of operations your application does at once. In this profile, you would only do non blocking operations in the default execution context, and then dispatch blocking operations to different execution contexts for those specific operations. + +In this case, you might create a number of different execution contexts for different types of operations, like this: + +@[many-specific-contexts](code/ThreadPools.scala) + +These might then be configured like so: + +@[many-specific-config](code/ThreadPools.scala) + +Then in your code, you would create `Future`s and pass the relevant `ExecutionContext` for the type of work that `Future` was doing. + +> **Note:** The configuration namespace can be chosen freely, as long as it matches the dispatcher ID passed to `app.actorSystem.dispatchers.lookup`. The `CustomExecutionContext` class will do this for you automatically. + +### Few specific thread pools + +This is a combination between the many specific thread pools and the highly synchronized profile. You would do most simple IO in the default execution context and set the number of threads there to be reasonably high (say 100), but then dispatch certain expensive operations to specific contexts, where you can limit the number of them that are done at one time. + +## Debugging Thread Pools + +There are many possible settings for a dispatcher, and it can be hard to see which ones have been applied and what the defaults are, particularly when overriding the default dispatcher. The `akka.log-config-on-start` configuration option shows the entire applied configuration when the application is loaded: + + +``` +akka.log-config-on-start = on +``` + +Note that you must have Akka logging set to a debug level to see output, so you should add the following to `logback.xml`: + +``` + +``` + +Once you see the logged HOCON output, you can copy and paste it into an "example.conf" file and view it in IntelliJ IDEA, which supports HOCON syntax. You should see your changes merged in with Akka's dispatcher, so if you override `thread-pool-executor` you will see it merged: + +``` +{ + # Elided HOCON... + "actor" : { + "default-dispatcher" : { + # application.conf @ file:/Users/wsargent/work/catapi/target/universal/stage/conf/application.conf: 19 + "executor" : "thread-pool-executor" + } + } +} +``` + +Note also that Play has different configuration settings for development mode than it does for production. To ensure that the thread pool settings are correct, you should run Play in a [[production configuration|Deploying#Running-a-test-instance]]. diff --git a/manual/working/commonGuide/configuration/WsCache.md b/manual/working/commonGuide/configuration/WsCache.md new file mode 100644 index 00000000..06664d66 --- /dev/null +++ b/manual/working/commonGuide/configuration/WsCache.md @@ -0,0 +1,89 @@ + +# Configuring WS Cache + +[[Play WS|ScalaWS]] allows you to set up HTTP caching from configuration. + +## Enabling Cache + +You must have a [JSR 107](https://www.jcp.org/en/jsr/detail?id=107) cache implementation (aka JCache) available in your Play application to use Play WS's cache facility. Play comes with an implementation that uses ehcache, so the easiest implementation is to add the following to `build.sbt`: + +@[play-ws-cache-deps](code/build.sbt) + +And enable the HTTP cache by adding the following to `application.conf` + +``` +play.ws.cache.enabled=true +``` + +If no JCache implementation is found, then Play WS will use an HTTP Cache with a stub cache that does not store anything. + +## Enabling Freshness Heuristics + +By default, Play WS does not cache HTTP responses when no explicit cache information is passed in. However, HTTP caching does have an option to cache pages based off heuristics so that you can cache responses even without co-operation from the remote server. + +To enable heuristics, set the following in `application.conf`: + +``` +play.ws.cache.heuristics.enabled=true +``` + +Play WS uses the [LM-Factor algorithm]( https://publicobject.com/2015/03/26/how-do-http-caching-heuristics-work/) to cache HTTP responses. + +## Limiting Cache Size + +Cache size is limited by the underlying cache implementation. Play WS will create a generic cache if no cache was found, but you should bound the cache explicitly, as JCache does not provide many options. + +> **NOTE**: If you do not limit the HTTP cache or expire elements in the cache, then you may cause the JVM to run out of memory. + +In ehcache, you can specify an existing cache by specifying [CacheManager](https://static.javadoc.io/javax.cache/cache-api/1.0.0/javax/cache/CacheManager.html) resource explicitly, which is used in `cachingProvider.getCacheManager(uri, environment.classLoader)`: + +``` +play.ws.cache.cacheManagerResource="ehcache-play-ws-cache.xml" +``` + +and then adding a cache such as following into the `conf` directory: + +```xml + + + + + +``` + +> **NOTE**: `play.ws.cache.cacheManagerURI` is deprecated, use `play.ws.cache.cacheManagerResource` with a path on the classpath instead. + +## Debugging the Cache + +To see exactly what the HTTP caching layer in Play WS is doing, please add the following to `logback.xml`: + +``` + +``` + +## Defining a Caching Provider + +You can define a specific [CachingProvider](https://static.javadoc.io/javax.cache/cache-api/1.0.0/javax/cache/spi/CachingProvider.html) for the WS cache, even if you are already using `ehcache` as a caching provider for Play Cache. For example, you can load the [Caffeine](https://github.com/ben-manes/caffeine/wiki) library: + +``` +// https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/jcache +libraryDependencies += "com.github.ben-manes.caffeine" % "jcache" % "2.4.0" +``` + +and then specify [Caffeine JCache](https://github.com/ben-manes/caffeine/wiki/JCache) as the caching provider: + +``` +play.ws.cache.cachingProviderName="" +``` + +## Reference Configuration + +The reference configuration shows the default settings for Play WS Caching: + +@[](/confs/play-ahc-ws/reference.conf) diff --git a/manual/working/commonGuide/configuration/WsSSL.md b/manual/working/commonGuide/configuration/WsSSL.md new file mode 100644 index 00000000..0c2c3772 --- /dev/null +++ b/manual/working/commonGuide/configuration/WsSSL.md @@ -0,0 +1,44 @@ + +# Configuring WS SSL + +[[Play WS|ScalaWS]] allows you to set up HTTPS completely from a configuration file, without the need to write code. It does this by layering the Java Secure Socket Extension (JSSE) with a configuration layer and with reasonable defaults. + +JDK 1.8 contains an implementation of JSSE which is [significantly more advanced](https://docs.oracle.com/javase/8/docs/technotes/guides/security/enhancements-8.html) than previous versions, and should be used if security is a priority. + +> **NOTE**: It is highly recommended (if not required) to use WS SSL with the +unlimited strength java cryptography extension. You can download the policy files from Oracle's website at [Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 8 Download](https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html). + +## Table of Contents + +The Play WS configuration is based on [Typesafe SSLConfig](https://lightbend.github.io/ssl-config). + +For convenience, a table of contents to SSLConfig is provided: + +- [Quick Start to WS SSL](https://lightbend.github.io/ssl-config/WSQuickStart.html) +- [Generating X.509 Certificates](https://lightbend.github.io/ssl-config/CertificateGeneration.html) +- [Configuring Trust Stores and Key Stores](https://lightbend.github.io/ssl-config/KeyStores.html) +- [Configuring Protocols](https://lightbend.github.io/ssl-config/Protocols.html) +- [Configuring Cipher Suites](https://lightbend.github.io/ssl-config/CipherSuites.html) +- [Configuring Certificate Validation](https://lightbend.github.io/ssl-config/CertificateValidation.html) +- [Configuring Certificate Revocation](https://lightbend.github.io/ssl-config/CertificateRevocation.html) +- [Configuring Hostname Verification](https://lightbend.github.io/ssl-config/HostnameVerification.html) +- [Example Configurations](https://lightbend.github.io/ssl-config/ExampleSSLConfig.html) +- [Using the Default SSLContext](https://lightbend.github.io/ssl-config/DefaultContext.html) +- [Debugging SSL Connections](https://lightbend.github.io/ssl-config/DebuggingSSL.html) +- [Loose Options](https://lightbend.github.io/ssl-config/LooseSSL.html) +- [Testing SSL](https://lightbend.github.io/ssl-config/TestingSSL.html) + +> **NOTE**: The links below are relative to `Typesafe SSLConfig`, which uses the `ssl-config` as a prefix for ssl properties.
+> Play uses the `play.ws.ssl` prefix, so that, for instance the `ssl-config.loose.acceptAnyCertificate` becomes `play.ws.ssl.loose.acceptAnyCertificate` for your play `WSClient` configuration. + +## Further Reading + +JSSE is a complex product. For convenience, the JSSE materials are provided here: + +JDK 1.8: + +* [JSSE Reference Guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html) +* [JSSE Crypto Spec](https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#SSLTLS) +* [SunJSSE Providers](https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html#SunJSSEProvider) +* [PKI Programmer's Guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html) +* [keytool](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html) diff --git a/manual/working/commonGuide/configuration/code/Configuration.scala b/manual/working/commonGuide/configuration/code/Configuration.scala new file mode 100644 index 00000000..86ce74a7 --- /dev/null +++ b/manual/working/commonGuide/configuration/code/Configuration.scala @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// With Play Scala + +object DependencyInjection { + //#dependency-injection + import javax.inject._ + import play.api.Configuration + + class MyController @Inject()(config: Configuration) { + // ... + } + //#dependency-injection +} diff --git a/manual/working/commonGuide/configuration/code/CustomAkkaHttpServer.scala b/manual/working/commonGuide/configuration/code/CustomAkkaHttpServer.scala new file mode 100644 index 00000000..e23aa195 --- /dev/null +++ b/manual/working/commonGuide/configuration/code/CustomAkkaHttpServer.scala @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +//#custom-akka-http-server +//###replace: package server +package detailedtopics.configuration.customakkaserver + +import java.util.Random +import play.core.server.AkkaHttpServer +import play.core.server.AkkaHttpServerProvider +import play.core.server.ServerProvider +import akka.http.scaladsl.ConnectionContext +import akka.http.scaladsl.model.HttpMethod +import akka.http.scaladsl.settings.ParserSettings +import akka.http.scaladsl.settings.ServerSettings + +/** A custom Akka HTTP server with advanced configuration. */ +class CustomAkkaHttpServer(context: AkkaHttpServer.Context) extends AkkaHttpServer(context) { + protected override def createParserSettings(): ParserSettings = { + val defaultSettings: ParserSettings = + super.createParserSettings() + defaultSettings.withCustomMethods(HttpMethod.custom("TICKLE")) + } + protected override def createServerSettings( + port: Int, + connectionContext: ConnectionContext, + secure: Boolean + ): ServerSettings = { + val defaultSettings: ServerSettings = + super.createServerSettings(port, connectionContext, secure) + defaultSettings.withWebsocketRandomFactory(() => new Random()) + } +} + +/** A factory that instantiates a CustomAkkaHttpServer. */ +class CustomAkkaHttpServerProvider extends ServerProvider { + def createServer(context: ServerProvider.Context) = { + val serverContext = AkkaHttpServer.Context.fromServerProviderContext(context) + new CustomAkkaHttpServer(serverContext) + } +} +//#custom-akka-http-server diff --git a/manual/working/commonGuide/configuration/code/JavaLog4JLoggerConfigurator.java b/manual/working/commonGuide/configuration/code/JavaLog4JLoggerConfigurator.java new file mode 100644 index 00000000..7d734b45 --- /dev/null +++ b/manual/working/commonGuide/configuration/code/JavaLog4JLoggerConfigurator.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// #log4j2-class +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import org.slf4j.ILoggerFactory; +import play.Environment; +import play.LoggerConfigurator; +import play.Mode; +import play.api.PlayException; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +// ###skip: 1 +/* +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.*; +import org.apache.logging.log4j.core.config.Configurator; +//###skip: 1 +*/ + +public class JavaLog4JLoggerConfigurator implements LoggerConfigurator { + + private ILoggerFactory factory; + + @Override + public void init(File rootPath, Mode mode) { + Map properties = new HashMap<>(); + properties.put("application.home", rootPath.getAbsolutePath()); + + String resourceName = "log4j2.xml"; + URL resourceUrl = this.getClass().getClassLoader().getResource(resourceName); + configure(properties, Optional.ofNullable(resourceUrl)); + } + + @Override + public void configure(Environment env) { + Map properties = + LoggerConfigurator.generateProperties(env, ConfigFactory.empty(), Collections.emptyMap()); + URL resourceUrl = env.resource("log4j2.xml"); + configure(properties, Optional.ofNullable(resourceUrl)); + } + + @Override + public void configure( + Environment env, Config configuration, Map optionalProperties) { + // LoggerConfigurator.generateProperties enables play.logger.includeConfigProperties=true + Map properties = + LoggerConfigurator.generateProperties(env, configuration, optionalProperties); + URL resourceUrl = env.resource("log4j2.xml"); + configure(properties, Optional.ofNullable(resourceUrl)); + } + + @Override + public void configure(Map properties, Optional config) { + try { + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + loggerContext.setConfigLocation(config.get().toURI()); + + factory = org.slf4j.impl.StaticLoggerBinder.getSingleton().getLoggerFactory(); + } catch (URISyntaxException ex) { + throw new PlayException( + "log4j2.xml resource was not found", + "Could not parse the location for log4j2.xml resource", + ex); + } + } + + @Override + public ILoggerFactory loggerFactory() { + return factory; + } + + @Override + public void shutdown() { + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(); + Configurator.shutdown(loggerContext); + } +} +// #log4j2-class diff --git a/manual/working/commonGuide/configuration/code/Log4j2LoggerConfigurator.scala b/manual/working/commonGuide/configuration/code/Log4j2LoggerConfigurator.scala new file mode 100644 index 00000000..5cd83189 --- /dev/null +++ b/manual/working/commonGuide/configuration/code/Log4j2LoggerConfigurator.scala @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +//#log4j2-class +import java.io.File +import java.net.URI +import java.net.URL + +//###skip: 1 +/* +import play.api.{Mode, Configuration, Environment, LoggerConfigurator} + +import org.slf4j.ILoggerFactory + +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.core._ +import org.apache.logging.log4j.core.config.Configurator +//###skip: 1 + */ + +import play.api.Mode +import play.api.Configuration +import play.api.Environment +import play.api.LoggerConfigurator +import org.slf4j.ILoggerFactory + +class Log4J2LoggerConfigurator extends LoggerConfigurator { + + private var factory: ILoggerFactory = _ + + override def init(rootPath: File, mode: Mode): Unit = { + val properties = Map("application.home" -> rootPath.getAbsolutePath) + val resourceName = "log4j2.xml" + val resourceUrl = Option(this.getClass.getClassLoader.getResource(resourceName)) + configure(properties, resourceUrl) + } + + override def shutdown(): Unit = { + val context = LogManager.getContext().asInstanceOf[LoggerContext] + Configurator.shutdown(context) + } + + override def configure(env: Environment): Unit = { + val properties = LoggerConfigurator.generateProperties(env, Configuration.empty, Map.empty) + val resourceUrl = env.resource("log4j2.xml") + configure(properties, resourceUrl) + } + + override def configure( + env: Environment, + configuration: Configuration, + optionalProperties: Map[String, String] + ): Unit = { + // LoggerConfigurator.generateProperties enables play.logger.includeConfigProperties=true + val properties = LoggerConfigurator.generateProperties(env, configuration, optionalProperties) + val resourceUrl = env.resource("log4j2.xml") + configure(properties, resourceUrl) + } + + override def configure(properties: Map[String, String], config: Option[URL]): Unit = { + val context = LogManager.getContext(false).asInstanceOf[LoggerContext] + context.setConfigLocation(config.get.toURI) + + factory = org.slf4j.impl.StaticLoggerBinder.getSingleton.getLoggerFactory + } + + override def loggerFactory: ILoggerFactory = factory +} +//#log4j2-class + +object Configurator { + def shutdown(context: Any): Unit = ??? + +} + +object LogManager { + def getContext(): LoggerContext = ??? + + def getContext(b: Boolean): LoggerContext = ??? + +} + +class LoggerContext { + def setConfigLocation(toURI: URI): Unit = ??? + +} diff --git a/manual/working/commonGuide/configuration/code/ThreadPools.scala b/manual/working/commonGuide/configuration/code/ThreadPools.scala new file mode 100644 index 00000000..224297eb --- /dev/null +++ b/manual/working/commonGuide/configuration/code/ThreadPools.scala @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package detailedtopics.configuration.threadpools + +import javax.inject.Inject + +import play.api.mvc._ +import play.api.test._ +import play.api._ +import com.typesafe.config.ConfigFactory +import akka.actor.ActorSystem + +import scala.concurrent.ExecutionContext +import scala.concurrent.Future +import scala.concurrent.TimeoutException + +import org.specs2.execute.AsResult + +class ThreadPoolsSpec extends PlaySpecification { + + "Play's thread pools" should { + + "make a global thread pool available" in new WithApplication() { + val controller = app.injector.instanceOf[Samples] + contentAsString(controller.someAsyncAction(FakeRequest())) must startWith("The answer is 42") + } + + "have a global configuration" in { + val config = """#default-config + akka { + actor { + default-dispatcher { + fork-join-executor { + # Settings this to 1 instead of 3 seems to improve performance. + parallelism-factor = 1.0 + + # @richdougherty: Not sure why this is set below the Akka + # default. + parallelism-max = 24 + + # Setting this to LIFO changes the fork-join-executor + # to use a stack discipline for task scheduling. This usually + # improves throughput at the cost of possibly increasing + # latency and risking task starvation (which should be rare). + task-peeking-mode = LIFO + } + } + } + } + #default-config """ + val parsed = ConfigFactory.parseString(config) + val actorSystem = ActorSystem("test", parsed.getConfig("akka")) + actorSystem.terminate() + success + } + + "use akka default thread pool configuration" in { + val config = """#akka-default-config + akka { + actor { + default-dispatcher { + # This will be used if you have set "executor = "fork-join-executor"" + fork-join-executor { + # Min number of threads to cap factor-based parallelism number to + parallelism-min = 8 + + # The parallelism factor is used to determine thread pool size using the + # following formula: ceil(available processors * factor). Resulting size + # is then bounded by the parallelism-min and parallelism-max values. + parallelism-factor = 3.0 + + # Max number of threads to cap factor-based parallelism number to + parallelism-max = 64 + + # Setting to "FIFO" to use queue like peeking mode which "poll" or "LIFO" to use stack + # like peeking mode which "pop". + task-peeking-mode = "FIFO" + } + } + } + } + #akka-default-config """ + val parsed = ConfigFactory.parseString(config) + val actorSystem = ActorSystem("test", parsed.getConfig("akka")) + actorSystem.terminate() + success + } + + "allow configuring a custom thread pool" in runningWithConfig( + """#my-context-config + my-context { + fork-join-executor { + parallelism-factor = 20.0 + parallelism-max = 200 + } + } + #my-context-config """ + ) { implicit app => + val akkaSystem = app.actorSystem + //#my-context-usage + val myExecutionContext: ExecutionContext = akkaSystem.dispatchers.lookup("my-context") + //#my-context-usage + await(Future(Thread.currentThread().getName)(myExecutionContext)) must startWith("application-my-context") + + //#my-context-explicit + Future { + // Some blocking or expensive code here + }(myExecutionContext) + //#my-context-explicit + + { + //#my-context-implicit + implicit val ec = myExecutionContext + + Future { + // Some blocking or expensive code here + } + //#my-context-implicit + } + success + } + + "allow access to the application classloader" in new WithApplication() { + val myClassName = "java.lang.String" + //#using-app-classloader + val myClass = app.classloader.loadClass(myClassName) + //#using-app-classloader + } + + "allow a synchronous thread pool" in { + val config = + ConfigFactory.parseString("""#highly-synchronous + akka { + actor { + default-dispatcher { + executor = "thread-pool-executor" + throughput = 1 + thread-pool-executor { + fixed-pool-size = 55 # db conn pool (50) + number of cores (4) + housekeeping (1) + } + } + } + } + #highly-synchronous """) + + val actorSystem = ActorSystem("test", config.getConfig("akka")) + actorSystem.terminate() + success + } + + "allow configuring many custom thread pools" in runningWithConfig( + """ #many-specific-config + contexts { + simple-db-lookups { + executor = "thread-pool-executor" + throughput = 1 + thread-pool-executor { + fixed-pool-size = 20 + } + } + expensive-db-lookups { + executor = "thread-pool-executor" + throughput = 1 + thread-pool-executor { + fixed-pool-size = 20 + } + } + db-write-operations { + executor = "thread-pool-executor" + throughput = 1 + thread-pool-executor { + fixed-pool-size = 10 + } + } + expensive-cpu-operations { + fork-join-executor { + parallelism-max = 2 + } + } + } + #many-specific-config """ + ) { implicit app => + val akkaSystem = app.actorSystem + //#many-specific-contexts + object Contexts { + implicit val simpleDbLookups: ExecutionContext = akkaSystem.dispatchers.lookup("contexts.simple-db-lookups") + implicit val expensiveDbLookups: ExecutionContext = + akkaSystem.dispatchers.lookup("contexts.expensive-db-lookups") + implicit val dbWriteOperations: ExecutionContext = akkaSystem.dispatchers.lookup("contexts.db-write-operations") + implicit val expensiveCpuOperations: ExecutionContext = + akkaSystem.dispatchers.lookup("contexts.expensive-cpu-operations") + } + //#many-specific-contexts + def test(context: ExecutionContext, name: String) = { + await(Future(Thread.currentThread().getName)(context)) must startWith("application-contexts." + name) + } + test(Contexts.simpleDbLookups, "simple-db-lookups") + test(Contexts.expensiveDbLookups, "expensive-db-lookups") + test(Contexts.dbWriteOperations, "db-write-operations") + test(Contexts.expensiveCpuOperations, "expensive-cpu-operations") + } + + } + + def runningWithConfig[T: AsResult](config: String)(block: Application => T) = { + val parsed: java.util.Map[String, Object] = ConfigFactory.parseString(config).root.unwrapped + running(_.configure(Configuration(ConfigFactory.parseString(config))))(block) + } +} + +// since specs provides defaultContext, implicitly importing it doesn't work +//#global-thread-pool +class Samples @Inject()(components: ControllerComponents)(implicit ec: ExecutionContext) + extends AbstractController(components) { + def someAsyncAction = Action.async { + someCalculation() + .map { result => + Ok(s"The answer is $result") + } + .recover { + case e: TimeoutException => + InternalServerError("Calculation timed out!") + } + } + + def someCalculation(): Future[Int] = { + Future.successful(42) + } +} +//#global-thread-pool diff --git a/manual/working/commonGuide/configuration/code/application.conf b/manual/working/commonGuide/configuration/code/application.conf new file mode 100644 index 00000000..2439072b --- /dev/null +++ b/manual/working/commonGuide/configuration/code/application.conf @@ -0,0 +1,4 @@ +//#custom-akka-http-server-provider +//###replace: play.server.provider = server.CustomAkkaHttpServerProvider +#play.server.provider = server.CustomAkkaHttpServerProvider +//#custom-akka-http-server-provider diff --git a/manual/working/commonGuide/configuration/code/build.sbt b/manual/working/commonGuide/configuration/code/build.sbt new file mode 100644 index 00000000..44afbec5 --- /dev/null +++ b/manual/working/commonGuide/configuration/code/build.sbt @@ -0,0 +1,12 @@ +//#play-ws-cache-deps +libraryDependencies += ws +libraryDependencies += ehcache +//#play-ws-cache-deps + +//#prefix-with-play-akka-dev-mode +PlayKeys.devSettings += "play.akka.dev-mode.akka.cluster.log-info" -> "off" +//#prefix-with-play-akka-dev-mode + +//#custom-akka-http-server-provider +PlayKeys.devSettings += "play.server.provider" -> "server.CustomAkkaHttpServerProvider" +//#custom-akka-http-server-provider diff --git a/manual/working/commonGuide/configuration/code/detailedtopics/ThreadPoolsJava.java b/manual/working/commonGuide/configuration/code/detailedtopics/ThreadPoolsJava.java new file mode 100644 index 00000000..affad01d --- /dev/null +++ b/manual/working/commonGuide/configuration/code/detailedtopics/ThreadPoolsJava.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package detailedtopics; + +import org.junit.Test; +import play.Application; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static play.test.Helpers.*; + +public class ThreadPoolsJava { + + @Test + public void usingAppClassLoader() throws Exception { + final Application app = fakeApplication(); + running( + app, + () -> { + String myClassName = "java.lang.String"; + try { + // #using-app-classloader + Class myClass = app.classloader().loadClass(myClassName); + // #using-app-classloader + assertThat(myClass, notNullValue()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + }); + } +} diff --git a/manual/working/commonGuide/configuration/code/detailedtopics/httpec/MyController.java b/manual/working/commonGuide/configuration/code/detailedtopics/httpec/MyController.java new file mode 100644 index 00000000..457d7dd6 --- /dev/null +++ b/manual/working/commonGuide/configuration/code/detailedtopics/httpec/MyController.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package detailedtopics.httpec; + +// #http-execution-context +import play.libs.concurrent.HttpExecutionContext; +import play.mvc.*; + +import javax.inject.Inject; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +public class MyController extends Controller { + + private HttpExecutionContext httpExecutionContext; + + @Inject + public MyController(HttpExecutionContext ec) { + this.httpExecutionContext = ec; + } + + public CompletionStage index() { + // Use a different task with explicit EC + return calculateResponse() + .thenApplyAsync( + answer -> { + // uses Http.Context + ctx().flash().put("info", "Response updated!"); + return ok("answer was " + answer); + }, + httpExecutionContext.current()); + } + + private static CompletionStage calculateResponse() { + return CompletableFuture.completedFuture("42"); + } +} +// #http-execution-context diff --git a/manual/working/commonGuide/configuration/code/javaguide/configuration/MyController.java b/manual/working/commonGuide/configuration/code/javaguide/configuration/MyController.java new file mode 100644 index 00000000..48b532e9 --- /dev/null +++ b/manual/working/commonGuide/configuration/code/javaguide/configuration/MyController.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// #dependency-injection +// ###replace: package controllers +package javaguide.configuration; + +import com.typesafe.config.Config; +import play.mvc.Controller; + +import javax.inject.Inject; + +public class MyController extends Controller { + + private final Config config; + + @Inject + public MyController(Config config) { + this.config = config; + } +} +// #dependency-injection diff --git a/manual/working/commonGuide/configuration/index.toc b/manual/working/commonGuide/configuration/index.toc new file mode 100644 index 00000000..79ea380e --- /dev/null +++ b/manual/working/commonGuide/configuration/index.toc @@ -0,0 +1,11 @@ +Configuration:Configuration +ConfigFile:Configuration file syntax and features +ApplicationSecret:Configuring the application secret +SettingsSession:Configuring the session cookie +SettingsJDBC:Configuring the JDBC connection pool +ThreadPools:Configuring Play's thread pools +SettingsAkkaHttp:Configuring Akka Http Server Backend +SettingsNetty:Configuring Netty Server Backend +SettingsLogger:Configuring logging +WsSSL:Configuring WS SSL +WsCache:Configuring WS Cache \ No newline at end of file diff --git a/manual/working/commonGuide/database/Databases.md b/manual/working/commonGuide/database/Databases.md new file mode 100644 index 00000000..ac18893f --- /dev/null +++ b/manual/working/commonGuide/database/Databases.md @@ -0,0 +1,7 @@ + +# Databases + +This section covers a some topics related to working with databases in Play. There is language-specific documentation about working with databases in the [[Java|JavaDatabase]] and [[Scala|ScalaDatabase]] guides. + +- [[Using an in memory H2 database|Developing-with-the-H2-Database]] +- [[Managing database evolutions|Evolutions]] diff --git a/manual/detailedTopics/database/Developing-with-the-H2-Database.md b/manual/working/commonGuide/database/Developing-with-the-H2-Database.md similarity index 68% rename from manual/detailedTopics/database/Developing-with-the-H2-Database.md rename to manual/working/commonGuide/database/Developing-with-the-H2-Database.md index 1e0935c1..4939f1a2 100644 --- a/manual/detailedTopics/database/Developing-with-the-H2-Database.md +++ b/manual/working/commonGuide/database/Developing-with-the-H2-Database.md @@ -1,7 +1,13 @@ - + # H2 database -The H2 in memory database is very convenient for development because your evolutions are run from scratch when play is restarted. If you are using anorm you probably need it to closely mimic your planned production database. To tell h2 that you want to mimic a particular database you add a parameter to the database url in your application.conf file, for example: +> **Note:** From Play 2.6.x onwards you actually need to include the H2 Dependency on your own. To do this you just need to add the following to your build.sbt: +> +> ``` +> libraryDependencies += "com.h2database" % "h2" % "1.4.192" +> ``` + +The H2 in memory database is very convenient for development because your evolutions are run from scratch when play is restarted. If you are using Anorm, you probably need it to closely mimic your planned production database. To tell h2 that you want to mimic a particular database you add a parameter to the database url in your application.conf file, for example: ``` db.default.url="jdbc:h2:mem:play;MODE=MYSQL" @@ -60,13 +66,15 @@ db.default.url="jdbc:h2:mem:play;MODE=MYSQL" H2, by default, drops your in memory database if there are no connections to it anymore. You probably don't want this to happen. To prevent this add `DB_CLOSE_DELAY=-1` to the url (use a semicolon as a separator) eg: `jdbc:h2:mem:play;MODE=MYSQL;DB_CLOSE_DELAY=-1` +> **Note:** Play's builtin JDBC Module will automatically add `DB_CLOSE_DELAY=-1`, however if you are using play-slick with evolutions you need to manually add `;DB_CLOSE_DELAY=-1` to your database url, else the evolution will be in a endless loop since the play application will restart after the evolutions are run, so that the applied evolutions will directly be lost. + ## Caveats H2, by default, creates tables with upper case names. Sometimes you don't want this to happen, for example when using H2 with Play evolutions in some compatibility modes. To prevent this add `DATABASE_TO_UPPER=FALSE` to the url (use a semicolon as a separator) eg: `jdbc:h2:mem:play;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=FALSE` ## H2 Browser -You can browse the contents of your database by typing `h2-browser` at the play console. An SQL browser will run in your web browser. +You can browse the contents of your database by typing `h2-browser` at the [sbt shell](https://www.scala-sbt.org/0.13/docs/Howto-Interactive-Mode.html). An SQL browser will run in your web browser. ## H2 Documentation diff --git a/manual/detailedTopics/evolutions/Evolutions.md b/manual/working/commonGuide/database/Evolutions.md similarity index 79% rename from manual/detailedTopics/evolutions/Evolutions.md rename to manual/working/commonGuide/database/Evolutions.md index a93c3819..8716e1d6 100644 --- a/manual/detailedTopics/evolutions/Evolutions.md +++ b/manual/working/commonGuide/database/Evolutions.md @@ -1,4 +1,4 @@ - + # Managing database evolutions When you use a relational database, you need a way to track and organize your database schema evolutions. Typically there are several situations where you need a more sophisticated way to track your database schema changes: @@ -9,12 +9,20 @@ When you use a relational database, you need a way to track and organize your da ## Enable evolutions -Add `evolutions` into your dependencies list. For example, in `build.sbt`: +Add `evolutions` and `jdbc` into your dependencies list. For example, in `build.sbt`: ```scala -libraryDependencies += evolutions +libraryDependencies ++= Seq(evolutions, jdbc) ``` +### Running evolutions using compile-time DI + +If you are using [[compile-time dependency injection|ScalaCompileTimeDependencyInjection]], you will need to mix in the `EvolutionsComponents` trait to your cake to get access to the `ApplicationEvolutions`, which will run the evolutions when instantiated. `EvolutionsComponents` requires `dbApi` to be defined, which you can get by mixing in `DBComponents` and `HikariCPComponents` (or `BoneCPComponents` if you are using BoneCP instead). Since `applicationEvolutions` is a lazy val supplied by `EvolutionsComponents`, you need to access that val to make sure the evolutions run. For example you could explicitly access it in your `ApplicationLoader`, or have an explicit dependency from another component. + +Your models will need an instance of `Database` to make connections to your database, which can be obtained from `dbApi.database`. + +@[compile-time-di-evolutions](code/CompileTimeDIEvolutions.scala) + ## Evolutions scripts Play tracks your database evolutions using several evolutions script. These scripts are written in plain old SQL and should be located in the `conf/evolutions/{database name}` directory of your application. If the evolutions apply to your default database, this path is `conf/evolutions/default`. @@ -30,9 +38,9 @@ For example, take a look at this first evolution script that bootstrap a basic a ``` # Users schema - + # --- !Ups - + CREATE TABLE User ( id bigint(20) NOT NULL AUTO_INCREMENT, email varchar(255) NOT NULL, @@ -41,9 +49,9 @@ CREATE TABLE User ( isAdmin boolean NOT NULL, PRIMARY KEY (id) ); - + # --- !Downs - + DROP TABLE User; ``` @@ -53,7 +61,7 @@ As you see you have to delimit the both Ups and Downs section by using comments Evolutions are automatically activated if a database is configured in `application.conf` and evolution scripts are present. You can disable them by setting `play.evolutions.enabled=false`. For example when tests set up their own database you can disable evolutions for the test environment. -When evolutions are activated, Play will check your database schema state before each request in DEV mode, or before starting the application in PROD mode. In DEV mode, if your database schema is not up to date, an error page will suggest that you synchronise your database schema by running the appropriate SQL script. +When evolutions are activated, Play will check your database schema state before each request in DEV mode, or before starting the application in PROD mode. In DEV mode, if your database schema is not up to date, an error page will suggest that you synchronize your database schema by running the appropriate SQL script. [[images/evolutions.png]] @@ -64,6 +72,7 @@ If you agree with the SQL script, you can apply it directly by clicking on the Evolutions can be configured both globally and per datasource. For global configuration, keys should be prefixed with `play.evolutions`. For per datasource configuration, keys should be prefixed with `play.evolutions.db.`, for example `play.evolutions.db.default`. The following configuration options are supported: * `enabled` - Whether evolutions are enabled. If configured globally to be false, it disables the evolutions module altogether. Defaults to true. +* `schema` - Database schema in which the generated evolution and lock tables will be saved to. No schema is set by default. * `autocommit` - Whether autocommit should be used. If false, evolutions will be applied in a single transaction. Defaults to true. * `useLocks` - Whether a locks table should be used. This must be used if you have many Play nodes that may potentially run evolutions, but you want to ensure that only one does. It will create a table called `play_evolutions_lock`, and use a `SELECT FOR UPDATE NOWAIT` or `SELECT FOR UPDATE` to lock it. This will only work for Postgres, Oracle, and MySQL InnoDB. It will not work for other databases. Defaults to false. * `autoApply` - Whether evolutions should be automatically applied. In dev mode, this will cause both ups and downs evolutions to be automatically applied. In prod mode, it will cause only ups evolutions to be automatically applied. Defaults to false. @@ -73,11 +82,11 @@ For example, to enable `autoApply` for all evolutions, you might set `play.evolu ## Synchronizing concurrent changes -Now let’s imagine that we have two developers working on this project. Developer A will work on a feature that requires a new database table. So he will create the following `2.sql` evolution script: +Now let’s imagine that we have two developers working on this project. Jamie will work on a feature that requires a new database table. So Jamie will create the following `2.sql` evolution script: ``` # Add Post - + # --- !Ups CREATE TABLE Post ( id bigint(20) NOT NULL AUTO_INCREMENT, @@ -88,26 +97,26 @@ CREATE TABLE Post ( FOREIGN KEY (author_id) REFERENCES User(id), PRIMARY KEY (id) ); - + # --- !Downs DROP TABLE Post; ``` -Play will apply this evolution script to Developer A’s database. +Play will apply this evolution script to Jamie’s database. -On the other hand, developer B will work on a feature that requires altering the User table. So he will also create the following `2.sql` evolution script: +On the other hand, Robin will work on a feature that requires altering the User table. So Robin will also create the following `2.sql` evolution script: ``` # Update User - + # --- !Ups ALTER TABLE User ADD age INT; - + # --- !Downs ALTER TABLE User DROP age; ``` -Developer B finishes his feature and commits (let’s say they are using Git). Now developer A has to merge the his colleague’s work before continuing, so he runs git pull, and the merge has a conflict, like: +Robin finishes the feature and commits (let’s say by using Git). Now Jamie has to merge Robin’s work before continuing, so Jamie runs git pull, and the merge has a conflict, like: ``` Auto-merging db/evolutions/2.sql @@ -115,12 +124,12 @@ CONFLICT (add/add): Merge conflict in db/evolutions/2.sql Automatic merge failed; fix conflicts and then commit the result. ``` -Each developer has created a `2.sql` evolution script. So developer A needs to merge the contents of this file: +Each developer has created a `2.sql` evolution script. So Jamie needs to merge the contents of this file: ``` <<<<<<< HEAD # Add Post - + # --- !Ups CREATE TABLE Post ( id bigint(20) NOT NULL AUTO_INCREMENT, @@ -131,15 +140,15 @@ CREATE TABLE Post ( FOREIGN KEY (author_id) REFERENCES User(id), PRIMARY KEY (id) ); - + # --- !Downs DROP TABLE Post; ======= # Update User - + # --- !Ups ALTER TABLE User ADD age INT; - + # --- !Downs ALTER TABLE User DROP age; >>>>>>> devB @@ -149,10 +158,10 @@ The merge is really easy to do: ``` # Add Post and update User - + # --- !Ups ALTER TABLE User ADD age INT; - + CREATE TABLE Post ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, @@ -162,16 +171,16 @@ CREATE TABLE Post ( FOREIGN KEY (author_id) REFERENCES User(id), PRIMARY KEY (id) ); - + # --- !Downs ALTER TABLE User DROP age; - + DROP TABLE Post; ``` -This evolution script represents the new revision 2 of the database, that is different of the previous revision 2 that developer A has already applied. +This evolution script represents the new revision 2 of the database, that is different of the previous revision 2 that Jamie has already applied. -So Play will detect it and ask developer A to synchronize his database by first reverting the old revision 2 already applied, and by applying the new revision 2 script: +So Play will detect it and ask Jamie to synchronize the database by first reverting the old revision 2 already applied, and by applying the new revision 2 script: ## Inconsistent states @@ -181,10 +190,10 @@ For example, the Ups script of this evolution has an error: ``` # Add another column to User - + # --- !Ups ALTER TABLE Userxxx ADD company varchar(255); - + # --- !Downs ALTER TABLE User DROP company; ``` @@ -205,10 +214,10 @@ But because your evolution script has errors, you probably want to fix it. So yo ``` # Add another column to User - + # --- !Ups ALTER TABLE User ADD company varchar(255); - + # --- !Downs ALTER TABLE User DROP company; ``` diff --git a/manual/working/commonGuide/database/code/CompileTimeDIEvolutions.scala b/manual/working/commonGuide/database/code/CompileTimeDIEvolutions.scala new file mode 100644 index 00000000..aa15a6b7 --- /dev/null +++ b/manual/working/commonGuide/database/code/CompileTimeDIEvolutions.scala @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +//#compile-time-di-evolutions +import play.api.ApplicationLoader.Context +import play.api.BuiltInComponentsFromContext +import play.api.db.Database +import play.api.db.DBComponents +import play.api.db.HikariCPComponents +import play.api.db.evolutions.EvolutionsComponents +import play.api.routing.Router +import play.filters.HttpFiltersComponents + +class AppComponents(cntx: Context) + extends BuiltInComponentsFromContext(cntx) + with DBComponents + with EvolutionsComponents + with HikariCPComponents + with HttpFiltersComponents { + // this will actually run the database migrations on startup + applicationEvolutions + + //###skip: 1 + val router = Router.empty +} +//#compile-time-di-evolutions diff --git a/manual/detailedTopics/evolutions/images/evolutions.png b/manual/working/commonGuide/database/images/evolutions.png similarity index 100% rename from manual/detailedTopics/evolutions/images/evolutions.png rename to manual/working/commonGuide/database/images/evolutions.png diff --git a/manual/detailedTopics/evolutions/images/evolutionsError.png b/manual/working/commonGuide/database/images/evolutionsError.png similarity index 100% rename from manual/detailedTopics/evolutions/images/evolutionsError.png rename to manual/working/commonGuide/database/images/evolutionsError.png diff --git a/manual/working/commonGuide/database/index.toc b/manual/working/commonGuide/database/index.toc new file mode 100644 index 00000000..5aa80355 --- /dev/null +++ b/manual/working/commonGuide/database/index.toc @@ -0,0 +1,3 @@ +Databases:Databases +Developing-with-the-H2-Database:Using an in memory H2 database +Evolutions:Managing database evolutions \ No newline at end of file diff --git a/manual/working/commonGuide/filters/AllowedHostsFilter.md b/manual/working/commonGuide/filters/AllowedHostsFilter.md new file mode 100644 index 00000000..abbe834f --- /dev/null +++ b/manual/working/commonGuide/filters/AllowedHostsFilter.md @@ -0,0 +1,43 @@ + +# Allowed hosts filter + +Play provides a filter that lets you configure which hosts can access your application. This is useful to prevent cache poisoning attacks. For a detailed description of how this attack works, see [this blog post](https://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html). The filter introduces a whitelist of allowed hosts and sends a 400 (Bad Request) response to all requests with a host that do not match the whitelist. + +This is an important filter to use even in development, because DNS rebinding attacks can be used against a developer's instance of Play: see [Rails Webconsole DNS Rebinding](https://benmmurphy.github.io/blog/2016/07/11/rails-webconsole-dns-rebinding/) for an example of how short lived DNS rebinding can attack a server running on localhost. + +Note that if you are running a functional test against a Play application which has the AllowedHostsFilter, then `FakeRequest` and `Helpers.fakeRequest()` will create a request which already has `HOST` set to `localhost`. + +## Enabling the allowed hosts filter + +> **Note:** As of Play 2.6.x, the Allowed Hosts filter is included in Play's list of default filters that are applied automatically to projects. See [[the Filters page|Filters]] for more information. + +To enable the filter manually, add the allowed hosts filter to your filters in `application.conf`: + +``` +play.filters.enabled += play.filters.hosts.AllowedHostsFilter +``` + +## Configuring allowed hosts + +You can configure which hosts the filter allows using `application.conf`. See the Play filters [`reference.conf`](resources/confs/filters-helpers/reference.conf) to see the defaults. + +`play.filters.hosts.allowed` is a list of strings of the form `.example.com` or `example.com`. With a leading dot, the pattern will match example.com and all subdomains (`www.example.com`, `foo.example.com`, `foo.bar.example.com`, etc.). Without the leading dot it will just match the exact domain. If your application runs on a specific port, you can also include a port number, for instance `.example.com:8080`. + +You can use the `.` pattern to match all hosts (not recommended in production). Note that the filter also strips the dot character from the end of the host, so the `example.com` pattern will match `example.com.` + +An example configuration follows. + +``` +play.filters.hosts { + # Allow requests to example.com, its subdomains, and localhost:9000. + allowed = [".example.com", "localhost:9000"] +} +``` + +## Testing + +Because the AllowedHostsFilter filter is added automatically, functional tests need to have the Host HTTP header added. + +If you are using `FakeRequest` or `Helpers.fakeRequest`, then the `Host` HTTP header is added for you automatically. If you are using `play.mvc.Http.RequestBuilder`, then you may need to add your own line to add the header manually: + +@[test-with-request-builder](code/javaguide/detailed/filters/FiltersTest.java) diff --git a/manual/working/commonGuide/filters/CorsFilter.md b/manual/working/commonGuide/filters/CorsFilter.md new file mode 100644 index 00000000..0f02c531 --- /dev/null +++ b/manual/working/commonGuide/filters/CorsFilter.md @@ -0,0 +1,41 @@ + +# Cross-Origin Resource Sharing + +Play provides a filter that implements Cross-Origin Resource Sharing (CORS). + +CORS is a protocol that allows web applications to make requests from the browser across different domains. A full specification can be found [here](http://www.w3.org/TR/cors/). + +## Enabling the CORS filter + +To enable the CORS filter, add `play.filters.cors.CORSFilter` to `application.conf`: + +``` +play.filters.enabled += "play.filters.cors.CORSFilter" +``` + +## Configuring the CORS filter + +The filter can be configured from `application.conf`. For a full listing of configuration options, see the Play filters [`reference.conf`](resources/confs/filters-helpers/reference.conf). + +The available options include: + +* `play.filters.cors.pathPrefixes` - filter paths by a whitelist of path prefixes +* `play.filters.cors.allowedOrigins` - allow only requests with origins from a whitelist (by default all origins are allowed) +* `play.filters.cors.allowedHttpMethods` - allow only HTTP methods from a whitelist for preflight requests (by default all methods are allowed) +* `play.filters.cors.allowedHttpHeaders` - allow only HTTP headers from a whitelist for preflight requests (by default all headers are allowed) +* `play.filters.cors.exposedHeaders` - set custom HTTP headers to be exposed in the response (by default no headers are exposed) +* `play.filters.cors.supportsCredentials` - disable/enable support for credentials (by default credentials support is enabled) +* `play.filters.cors.preflightMaxAge` - set how long the results of a preflight request can be cached in a preflight result cache (by default 1 hour) +* `play.filters.cors.serveForbiddenOrigins` - enable/disable serving requests with origins not in whitelist as non-CORS requests (by default they are forbidden) + +For example: + +``` +play.filters.cors { + pathPrefixes = ["/some/path", ...] + allowedOrigins = ["http://www.example.com", ...] + allowedHttpMethods = ["GET", "POST"] + allowedHttpHeaders = ["Accept"] + preflightMaxAge = 3 days +} +``` diff --git a/manual/working/commonGuide/filters/Filters.md b/manual/working/commonGuide/filters/Filters.md new file mode 100644 index 00000000..13eb83cc --- /dev/null +++ b/manual/working/commonGuide/filters/Filters.md @@ -0,0 +1,135 @@ + +# Built-in HTTP filters + +Play provides several standard filters that can modify the HTTP behavior of your application. You can also write your own filters in either [[Java|JavaHttpFilters]] or [[Scala|ScalaHttpFilters]]. + +- [[Configuring gzip encoding|GzipEncoding]] +- [[Configuring security headers|SecurityHeaders]] +- [[Configuring CORS|CorsFilter]] +- [[Configuring allowed hosts|AllowedHostsFilter]] +- [[Configuring Redirect HTTPS filter|RedirectHttpsFilter]] + +## Default Filters + +Play now comes with a default set of enabled filters, defined through configuration. If the property `play.http.filters` is null, then the default is now `play.api.http.EnabledFilters`, which loads up the filters defined by fully qualified class name in the `play.filters.enabled` configuration property. + +In Play itself, `play.filters.enabled` is an empty list. However, the filters library is automatically loaded in sbt as an AutoPlugin called `PlayFilters`, and will append the following values to the `play.filters.enabled` property: + +* `play.filters.csrf.CSRFFilter` +* `play.filters.headers.SecurityHeadersFilter` +* `play.filters.hosts.AllowedHostsFilter` + +This means that on new projects, CSRF protection ([[ScalaCsrf]] / [[JavaCsrf]]), [[SecurityHeaders]] and [[AllowedHostsFilter]] are all defined automatically. + +To append to the defaults list, use the `+=`: + +``` +play.filters.enabled+=MyFilter +``` + +If you have previously defined your own filters by extending `play.api.http.DefaultHttpFilters`, then you can also combine `EnabledFilters` with your own filters in code: + +Java +: @[filters-combine-enabled-filters](code/javaguide/detailed/filters/Filters.java) + +Scala +: @[filters-combine-enabled-filters](code/scalaguide/detailed/filters/ScalaFilters.scala) + +Otherwise, if you have a `Filters` class in the root or have `play.http.filters` defined explicitly, it will take precedence over the `EnabledFilters` functionality described below. + +### Testing Default Filters + +Because there are several filters enabled, functional tests may need to change slightly to ensure that all the tests pass and requests are valid. For example, a request that does not have a `Host` HTTP header set to `localhost` will not pass the AllowedHostsFilter and will return a 400 Forbidden response instead. + +#### Testing with AllowedHostsFilter + +Because the AllowedHostsFilter filter is added automatically, functional tests need to have the Host HTTP header added. + +If you are using `FakeRequest` or `Helpers.fakeRequest`, then the `Host` HTTP header is added for you automatically. If you are using `play.mvc.Http.RequestBuilder`, then you may need to add your own line to add the header manually: + +@[test-with-request-builder](code/javaguide/detailed/filters/FiltersTest.java) + +#### Testing with CSRFFilter + +Because the CSRFFilter filter is added automatically, tests that render a Twirl template that includes `CSRF.formField`, i.e. + +```html +@(userForm: Form[UserData])(implicit request: RequestHeader, m: Messages) + +

user form

+ +@request.flash.get("success").getOrElse("") + +@helper.form(action = routes.UserController.userPost()) { + @helper.CSRF.formField + @helper.inputText(userForm("name")) + @helper.inputText(userForm("age")) + +} +``` + +must contain a CSRF token in the request. In the Scala API, this is done by importing `play.api.test.CSRFTokenHelper._`, which enriches `play.api.test.FakeRequest` with the `withCSRFToken` method: + +@[test-with-withCSRFToken](code/scalaguide/detailed/filters/UserControllerSpec.scala) + +In the Java API, this is done by calling `CSRFTokenHelper.addCSRFToken` on a `play.mvc.Http.RequestBuilder` instance: + +@[test-with-addCSRFToken](code/javaguide/detailed/filters/FiltersTest.java) + +### Disabling Default Filters + +The simplest way to disable a filter is to add it to the `play.filters.disabled` list in `application.conf`: + +``` +play.filters.disabled+=play.filters.hosts.AllowedHostsFilter +``` + +This may be useful if you have functional tests that you do not want to go through the default filters. + +To remove the default filters, you can set the entire list manually: + +``` +play.filters.enabled=[] +``` + +If you want to remove all filter classes, you can disable it through the `disablePlugins` mechanism: + +``` +lazy val root = project.in(file(".")).enablePlugins(PlayScala).disablePlugins(PlayFilters) +``` + +If you are writing functional tests involving `GuiceApplicationBuilder`, then you can disable all filters in a test by calling `configure`: + +@[test-disabling-filters](code/scalaguide/detailed/filters/UserControllerSpec.scala) + +## Compile Time Default Filters + +If you are using compile time dependency injection, then the default filters are resolved at compile time, rather than through runtime. + +This means that the [`play.api.BuiltInComponents`](api/scala/play/api/BuiltInComponents.html) trait (for Scala) and [`play.BuiltInComponents`](api/java/play/BuiltInComponents.html) interface (for Java) now contains an `httpFilters` method which is left abstract. The default list of filters is defined in [`play.filters.HttpFiltersComponents`](api/scala/play/filters/HttpFiltersComponents.html) for Scala and [`play.filters.components.HttpFiltersComponents`](api/java/play/filters/components/HttpFiltersComponents.html) for Java. So, for most cases you will want to mixin `HttpFiltersComponents` and append your own filters: + +Java +: @[appending-filters-compile-time-di](code/javaguide/detailed/filters/add/MyAppComponents.java) + +Scala +: @[appending-filters-compile-time-di](code/scalaguide/detailed/filters/FiltersComponents.scala) + +If you want to filter elements out of the list, you can do the following: + +Java +: @[removing-filters-compile-time-di](code/javaguide/detailed/filters/remove/MyAppComponents.java) + +Scala +: @[removing-filters-compile-time-di](code/scalaguide/detailed/filters/FiltersComponents.scala) + +### Disabling Compile Time Default Filters + +To disable the default filters, mix in [`play.api.NoHttpFiltersComponents`](api/scala/play/api/NoHttpFiltersComponents.html) for Scala and [`play.filters.components.NoHttpFiltersComponents`](api/java/play/filters/components/NoHttpFiltersComponents.html) for Java: + +Java +: @[remove-all-filters-compile-time-di](code/javaguide/detailed/filters/removeAll/MyAppComponents.java) + +Scala +: @[remove-all-filters-compile-time-di](code/scalaguide/detailed/filters/FiltersComponents.scala) + +Both Scala [`play.api.NoHttpFiltersComponents`](api/scala/play/api/NoHttpFiltersComponents.html) and [`play.filters.components.NoHttpFiltersComponents`](api/java/play/filters/components/NoHttpFiltersComponents.html) have `httpFilters` method which returns an empty list of filters. \ No newline at end of file diff --git a/manual/working/commonGuide/filters/GzipEncoding.md b/manual/working/commonGuide/filters/GzipEncoding.md new file mode 100644 index 00000000..a842a06e --- /dev/null +++ b/manual/working/commonGuide/filters/GzipEncoding.md @@ -0,0 +1,45 @@ + +# Configuring gzip encoding + +Play provides a gzip filter that can be used to gzip responses. + +## Enabling the gzip filter + +To enable the gzip filter, add the filter to `application.conf`: + +``` +play.filters.enabled += "play.filters.gzip.GzipFilter" +``` + +## Configuring the gzip filter + +The gzip filter supports a small number of tuning configuration options, which can be configured from `application.conf`. To see the available configuration options, see the Play filters [`reference.conf`](resources/confs/filters-helpers/reference.conf). + +## Controlling which responses are gzipped + +You can control which responses are and aren't gzipped based on their content types via `application.conf`: + +``` +play.filters.gzip { + + contentType { + + # If non empty, then a response will only be compressed if its content type is in this list. + whiteList = [ "text/*", "application/javascript", "application/json" ] + + # The black list is only used if the white list is empty. + # Compress all responses except the ones whose content type is in this list. + blackList = [] + } +} +``` + +As a more flexible alternative you can use the `shouldGzip` parameter of the gzip filter itself, which accepts a function of a request header and a response header to a boolean. + +For example, the code below only gzips HTML responses: + +Scala +: @[should-gzip](code/GzipEncoding.scala) + +Java +: @[gzip-filter](code/detailedtopics/configuration/gzipencoding/CustomFilters.java) diff --git a/manual/working/commonGuide/filters/RedirectHttpsFilter.md b/manual/working/commonGuide/filters/RedirectHttpsFilter.md new file mode 100644 index 00000000..7ccbf556 --- /dev/null +++ b/manual/working/commonGuide/filters/RedirectHttpsFilter.md @@ -0,0 +1,55 @@ + +# Redirect HTTPS Filter + +Play provides a filter which will redirect all HTTP requests to HTTPS automatically. + +## Enabling the HTTPS filter + +To enable the filter, add it to `play.filters.enabled`: + +``` +play.filters.enabled += play.filters.https.RedirectHttpsFilter +``` + +By default, the redirect only happens in Prod mode. To override this, set `play.filters.https.redirectEnabled = true`. + +## Determining Secure Requests + +The filter evaluates a request to be secure if `request.secure` is true. + +This logic depends on the [[trusted proxies|HTTPServer#configuring-trusted-proxies]] configured for Play's HTTP engine. Internally, `play.core.server.common.ForwardedHeaderHandler` and `play.api.mvc.request.RemoteConnection` determine between them whether an incoming request meets the criteria to be "secure", meaning that the request has gone through HTTPS at some point. + +When the filter is enabled, any request that is not secure is redirected. + +## Strict Transport Security + +The [Strict Transport Security](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) header is used to indicate when HTTPS should always be used, and is added to a secure request. The HSTS header is only added if the redirect is enabled. + +The default is "max-age=31536000; includeSubDomains", and can be set explicitly by adding the following to `application.conf`: + +``` +play.filters.https.strictTransportSecurity="max-age=31536000; includeSubDomains" +``` + +It is also possible to set `play.filters.https.strictTransportSecurity = null` to disable HSTS. + +Note that the `Strict-Transport-Security` header tells the browser to prefer HTTPS for *all requests to that hostname*, so if you enable the filter in dev mode, the header will affect other apps being developed with that hostname (e.g. `localhost:9000`). If you want to avoid this, either use a different host for each app in development (`app1:9000`, `app2:9000`, etc.) or disable HSTS completely in dev mode. + +## Redirect code + +The filter redirects using HTTP code 308, which is a permanent redirect that does not change the HTTP method according to [RFC 7238](https://tools.ietf.org/html/rfc7238). This will work with the vast majority of browsers, but you can change the redirect code if working with older browsers: + +``` +play.filters.https.redirectStatusCode = 301 +``` + +## Custom HTTPS Port + +If the HTTPS server is on a custom port, then the redirect URL needs to be aware of it. If the port is specified: + +``` +play.filters.https.port = 9443 +``` + +then the URL in the `Location` header will include the port specifically, e.g. `https://playframework.com:9443/some/url`. + diff --git a/manual/working/commonGuide/filters/SecurityHeaders.md b/manual/working/commonGuide/filters/SecurityHeaders.md new file mode 100644 index 00000000..abb8ab15 --- /dev/null +++ b/manual/working/commonGuide/filters/SecurityHeaders.md @@ -0,0 +1,45 @@ + +# Configuring Security Headers + +Play provides a security headers filter that can be used to configure some default headers in the HTTP response to mitigate security issues and provide an extra level of defense for new applications. + +## Enabling the security headers filter + +> **Note:** As of Play 2.6.x, the Security Headers filter is included in Play's list of default filters that are applied automatically to projects. See [[the Filters page|Filters]] for more information. + +To enable the security headers filter manually, add the security headers filter to your filters in `application.conf`: + +``` +play.filters.enabled += "play.filters.headers.SecurityHeadersFilter" +``` + +## Configuring the security headers + +Scaladoc is available in the [play.filters.headers](api/scala/play/filters/headers/index.html) package. + +The filter will set headers in the HTTP response automatically. The settings can be configured through the following settings in `application.conf` + +* `play.filters.headers.frameOptions` - sets [X-Frame-Options](https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options), "DENY" by default. +* `play.filters.headers.xssProtection` - sets [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection), "1; mode=block" by default. +* `play.filters.headers.contentTypeOptions` - sets [X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options), "nosniff" by default. +* `play.filters.headers.permittedCrossDomainPolicies` - sets [X-Permitted-Cross-Domain-Policies](https://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html), "master-only" by default. +* `play.filters.headers.referrerPolicy` - sets [Referrer Policy](https://www.w3.org/TR/referrer-policy/), "origin-when-cross-origin, strict-origin-when-cross-origin" by default. +* `play.filters.headers.contentSecurityPolicy` - sets [Content-Security-Policy](https://developers.google.com/web/fundamentals/security/csp/), "default-src 'self'" by default. + +Any of the headers can be disabled by setting a configuration value of `null`, for example: + +``` +play.filters.headers.frameOptions = null +``` + +For a full listing of configuration options, see the Play filters [`reference.conf`](resources/confs/filters-helpers/reference.conf). + +## Action-specific overrides + +Security headers may be overridden in specific actions using `withHeaders` on the result: + +@[allowActionSpecificHeaders](code/SecurityHeaders.scala) + +Any security headers not mentioned in `withHeaders` will use the usual configured values +(if present) or the defaults. Action-specific security headers are ignored unless +`play.filters.headers.allowActionSpecificHeaders` is set to `true` in the configuration. diff --git a/manual/working/commonGuide/filters/code/GzipEncoding.scala b/manual/working/commonGuide/filters/code/GzipEncoding.scala new file mode 100644 index 00000000..69e5bd1d --- /dev/null +++ b/manual/working/commonGuide/filters/code/GzipEncoding.scala @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package detailedtopics.configuration.gzipencoding + +import akka.stream.ActorMaterializer +import play.api.test._ + +class GzipEncoding extends PlaySpecification { + + import javax.inject.Inject + + import play.api.http.DefaultHttpFilters + import play.filters.gzip.GzipFilter + + class Filters @Inject()(gzipFilter: GzipFilter) extends DefaultHttpFilters(gzipFilter) + + "gzip filter" should { + + "allow custom strategies for when to gzip (Scala)" in { + + import play.api.mvc._ + running() { app => + implicit val mat = ActorMaterializer()(app.actorSystem) + def Action = app.injector.instanceOf[DefaultActionBuilder] + + val filter = + //#should-gzip + new GzipFilter( + shouldGzip = (request, response) => response.body.contentType.exists(_.startsWith("text/html")) + ) + //#should-gzip + + header(CONTENT_ENCODING, filter(Action(Results.Ok("foo")))(gzipRequest).run()) must beNone + } + } + + "allow custom strategies for when to gzip (Java)" in { + + import play.api.mvc._ + val app = play.api.inject.guice.GuiceApplicationBuilder().build() + running(app) { + implicit val mat = ActorMaterializer()(app.actorSystem) + def Action = app.injector.instanceOf[DefaultActionBuilder] + + val filter = (new CustomFilters(mat)).getFilters.get(0) + + header(CONTENT_ENCODING, filter(Action(Results.Ok("foo")))(gzipRequest).run()) must beNone + } + } + + } + + def gzipRequest = FakeRequest().withHeaders(ACCEPT_ENCODING -> "gzip") + +} diff --git a/manual/working/commonGuide/filters/code/SecurityHeaders.scala b/manual/working/commonGuide/filters/code/SecurityHeaders.scala new file mode 100644 index 00000000..435de7bf --- /dev/null +++ b/manual/working/commonGuide/filters/code/SecurityHeaders.scala @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package detailedtopics.configuration.securityheaders + +//#filters +import javax.inject.Inject + +import play.api.http.DefaultHttpFilters +import play.filters.headers.SecurityHeadersFilter +import play.api.mvc.BaseController +import play.api.mvc.ControllerComponents +//#filters + +class SecurityHeaders @Inject()(val controllerComponents: ControllerComponents) extends BaseController { + + def index = Action { + //#allowActionSpecificHeaders + Ok("Index").withHeaders(SecurityHeadersFilter.CONTENT_SECURITY_POLICY_HEADER -> "my page-specific header") + //#allowActionSpecificHeaders + } +} + +object SecurityHeaders { + class Filters @Inject()(securityHeadersFilter: SecurityHeadersFilter) + extends DefaultHttpFilters(securityHeadersFilter) +} diff --git a/manual/working/commonGuide/filters/code/detailedtopics/configuration/gzipencoding/CustomFilters.java b/manual/working/commonGuide/filters/code/detailedtopics/configuration/gzipencoding/CustomFilters.java new file mode 100644 index 00000000..edff7749 --- /dev/null +++ b/manual/working/commonGuide/filters/code/detailedtopics/configuration/gzipencoding/CustomFilters.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package detailedtopics.configuration.gzipencoding; + +import akka.stream.Materializer; +import play.mvc.EssentialFilter; +import play.filters.gzip.GzipFilter; +import play.filters.gzip.GzipFilterConfig; +import play.http.HttpFilters; +import play.mvc.Http; +import play.mvc.Result; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.BiFunction; + +public class CustomFilters implements HttpFilters { + + private List filters; + + @Inject + public CustomFilters(Materializer materializer) { + // #gzip-filter + GzipFilterConfig gzipFilterConfig = new GzipFilterConfig(); + GzipFilter gzipFilter = + new GzipFilter( + gzipFilterConfig.withShouldGzip( + (BiFunction) + (req, res) -> res.body().contentType().orElse("").startsWith("text/html")), + materializer); + // #gzip-filter + filters = Collections.singletonList(gzipFilter.asJava()); + } + + @Override + public List getFilters() { + return filters; + } +} diff --git a/manual/working/commonGuide/filters/code/filters.sbt b/manual/working/commonGuide/filters/code/filters.sbt new file mode 100644 index 00000000..94592fb2 --- /dev/null +++ b/manual/working/commonGuide/filters/code/filters.sbt @@ -0,0 +1,7 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + +//#content +libraryDependencies += filters +//#content diff --git a/manual/working/commonGuide/filters/code/javaguide/detailed/filters/Filters.java b/manual/working/commonGuide/filters/code/javaguide/detailed/filters/Filters.java new file mode 100644 index 00000000..b5265233 --- /dev/null +++ b/manual/working/commonGuide/filters/code/javaguide/detailed/filters/Filters.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.detailed.filters; + +// #filters-combine-enabled-filters +import play.api.http.EnabledFilters; +import play.filters.cors.CORSFilter; +import play.http.DefaultHttpFilters; +import play.mvc.EssentialFilter; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Filters extends DefaultHttpFilters { + + @Inject + public Filters(EnabledFilters enabledFilters, CORSFilter corsFilter) { + super(combine(enabledFilters.asJava().getFilters(), corsFilter.asJava())); + } + + private static List combine( + List filters, EssentialFilter toAppend) { + List combinedFilters = new ArrayList<>(filters); + combinedFilters.add(toAppend); + return combinedFilters; + } +} +// #filters-combine-enabled-filters diff --git a/manual/working/commonGuide/filters/code/javaguide/detailed/filters/FiltersTest.java b/manual/working/commonGuide/filters/code/javaguide/detailed/filters/FiltersTest.java new file mode 100644 index 00000000..efe00271 --- /dev/null +++ b/manual/working/commonGuide/filters/code/javaguide/detailed/filters/FiltersTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.detailed.filters; + +import org.junit.Test; +import play.api.mvc.PlayBodyParsers; +import play.api.test.CSRFTokenHelper; +import play.core.j.JavaContextComponents; +import play.mvc.Http; +import play.mvc.Results; +import play.routing.Router; +import play.routing.RoutingDsl; +import play.test.Helpers; +import play.test.WithApplication; + +import static play.test.Helpers.GET; +import static play.test.Helpers.POST; + +public class FiltersTest extends WithApplication { + + @Test + public void testRequestBuilder() { + Router router = + new RoutingDsl( + instanceOf(play.mvc.BodyParser.Default.class), + instanceOf(JavaContextComponents.class)) + .GET("/xx/Kiwi") + .routeTo(() -> Results.ok("success")) + .build(); + + // #test-with-request-builder + Http.RequestBuilder request = + new Http.RequestBuilder() + .method(GET) + .header(Http.HeaderNames.HOST, "localhost") + .uri("/xx/Kiwi"); + // #test-with-request-builder + + Helpers.routeAndCall(app, router, request, 10_000 /* 10 seconds */); + } + + @Test + public void test() { + Router router = + new RoutingDsl( + instanceOf(play.mvc.BodyParser.Default.class), + instanceOf(JavaContextComponents.class)) + .POST("/xx/Kiwi") + .routeTo(() -> Results.ok("success")) + .build(); + + // #test-with-addCSRFToken + Http.RequestBuilder request = new Http.RequestBuilder().method(POST).uri("/xx/Kiwi"); + + request = CSRFTokenHelper.addCSRFToken(request); + // #test-with-addCSRFToken + + Helpers.routeAndCall(app, router, request, 10_000 /* 10 seconds */); + } +} diff --git a/manual/working/commonGuide/filters/code/javaguide/detailed/filters/add/MyAppComponents.java b/manual/working/commonGuide/filters/code/javaguide/detailed/filters/add/MyAppComponents.java new file mode 100644 index 00000000..caf44b5d --- /dev/null +++ b/manual/working/commonGuide/filters/code/javaguide/detailed/filters/add/MyAppComponents.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.detailed.filters.add; + +import javaguide.application.httpfilters.LoggingFilter; + +// #appending-filters-compile-time-di +import play.ApplicationLoader; +import play.BuiltInComponentsFromContext; +import play.filters.components.HttpFiltersComponents; +import play.mvc.EssentialFilter; +import play.routing.Router; + +import java.util.ArrayList; +import java.util.List; + +public class MyAppComponents extends BuiltInComponentsFromContext implements HttpFiltersComponents { + + public MyAppComponents(ApplicationLoader.Context context) { + super(context); + } + + @Override + public List httpFilters() { + List combinedFilters = + new ArrayList<>(HttpFiltersComponents.super.httpFilters()); + combinedFilters.add(new LoggingFilter(materializer())); + return combinedFilters; + } + + @Override + public Router router() { + return Router.empty(); // implement the router as needed + } +} +// #appending-filters-compile-time-di diff --git a/manual/working/commonGuide/filters/code/javaguide/detailed/filters/remove/MyAppComponents.java b/manual/working/commonGuide/filters/code/javaguide/detailed/filters/remove/MyAppComponents.java new file mode 100644 index 00000000..55300d83 --- /dev/null +++ b/manual/working/commonGuide/filters/code/javaguide/detailed/filters/remove/MyAppComponents.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.detailed.filters.remove; + +import play.ApplicationLoader; +import play.BuiltInComponentsFromContext; +import play.filters.components.HttpFiltersComponents; +import play.filters.csrf.CSRFFilter; +import play.mvc.EssentialFilter; +import play.routing.Router; + +import java.util.List; +import java.util.stream.Collectors; + +// #removing-filters-compile-time-di +public class MyAppComponents extends BuiltInComponentsFromContext implements HttpFiltersComponents { + + public MyAppComponents(ApplicationLoader.Context context) { + super(context); + } + + @Override + public List httpFilters() { + return HttpFiltersComponents.super.httpFilters().stream() + .filter(filter -> !filter.getClass().equals(CSRFFilter.class)) + .collect(Collectors.toList()); + } + + @Override + public Router router() { + return Router.empty(); // implement the router as needed + } +} +// #removing-filters-compile-time-di diff --git a/manual/working/commonGuide/filters/code/javaguide/detailed/filters/removeAll/MyAppComponents.java b/manual/working/commonGuide/filters/code/javaguide/detailed/filters/removeAll/MyAppComponents.java new file mode 100644 index 00000000..3d274ff3 --- /dev/null +++ b/manual/working/commonGuide/filters/code/javaguide/detailed/filters/removeAll/MyAppComponents.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.detailed.filters.removeAll; + +import play.ApplicationLoader; +import play.BuiltInComponentsFromContext; +import play.filters.components.NoHttpFiltersComponents; +import play.routing.Router; + +// #remove-all-filters-compile-time-di +public class MyAppComponents extends BuiltInComponentsFromContext + implements NoHttpFiltersComponents { + + public MyAppComponents(ApplicationLoader.Context context) { + super(context); + } + + // no need to override httpFilters method + + @Override + public Router router() { + return Router.empty(); // implement the router as needed + } +} +// #remove-all-filters-compile-time-di diff --git a/manual/working/commonGuide/filters/code/scalaguide/detailed/filters/FiltersComponents.scala b/manual/working/commonGuide/filters/code/scalaguide/detailed/filters/FiltersComponents.scala new file mode 100644 index 00000000..4698bf5d --- /dev/null +++ b/manual/working/commonGuide/filters/code/scalaguide/detailed/filters/FiltersComponents.scala @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package scalaguide.detailed.filters + +// #appending-filters-compile-time-di +import akka.util.ByteString +import play.api.ApplicationLoader +import play.api.BuiltInComponentsFromContext +import play.api.NoHttpFiltersComponents +import play.api.libs.streams.Accumulator +import play.api.mvc.EssentialAction +import play.api.mvc.EssentialFilter +import play.api.mvc.RequestHeader +import play.api.mvc.Result +import play.api.routing.Router +import play.filters.csrf.CSRFFilter + +// ###replace: class MyAppComponents(context: ApplicationLoader.Context) +class AddHttpFiltersComponents(context: ApplicationLoader.Context) + extends BuiltInComponentsFromContext(context) + with play.filters.HttpFiltersComponents { + + lazy val loggingFilter = new LoggingFilter() + + override def httpFilters: Seq[EssentialFilter] = { + super.httpFilters :+ loggingFilter + } + + override def router: Router = Router.empty // implement the router as needed +} +// #appending-filters-compile-time-di + +// #removing-filters-compile-time-di +// ###replace: class MyAppComponents(context: ApplicationLoader.Context) +class RemoveHttpFilterComponents(context: ApplicationLoader.Context) + extends BuiltInComponentsFromContext(context) + with play.filters.HttpFiltersComponents { + + override def httpFilters: Seq[EssentialFilter] = { + super.httpFilters.filterNot(_.getClass == classOf[CSRFFilter]) + } + + override def router: Router = Router.empty // implement the router as needed +} +// #removing-filters-compile-time-di + +// #remove-all-filters-compile-time-di +// ###replace: class MyAppComponents(context: ApplicationLoader.Context) +class RemoveAllHttpFiltersComponents(context: ApplicationLoader.Context) + extends BuiltInComponentsFromContext(context) + with NoHttpFiltersComponents { + + override def router: Router = Router.empty // implement the router as needed + +} +// #remove-all-filters-compile-time-di + +class LoggingFilter extends EssentialFilter { + override def apply(next: EssentialAction): EssentialAction = new EssentialAction { + override def apply(request: RequestHeader): Accumulator[ByteString, Result] = next(request) + } +} diff --git a/manual/working/commonGuide/filters/code/scalaguide/detailed/filters/ScalaFilters.scala b/manual/working/commonGuide/filters/code/scalaguide/detailed/filters/ScalaFilters.scala new file mode 100644 index 00000000..766fd9e3 --- /dev/null +++ b/manual/working/commonGuide/filters/code/scalaguide/detailed/filters/ScalaFilters.scala @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package scalaguide.detailed.filters + +// #filters-combine-enabled-filters +import javax.inject.Inject + +import play.filters.cors.CORSFilter +import play.api.http.DefaultHttpFilters +import play.api.http.EnabledFilters + +class Filters @Inject()(enabledFilters: EnabledFilters, corsFilter: CORSFilter) + extends DefaultHttpFilters(enabledFilters.filters :+ corsFilter: _*) + +// #filters-combine-enabled-filters diff --git a/manual/working/commonGuide/filters/code/scalaguide/detailed/filters/UserControllerSpec.scala b/manual/working/commonGuide/filters/code/scalaguide/detailed/filters/UserControllerSpec.scala new file mode 100644 index 00000000..b53fc1a1 --- /dev/null +++ b/manual/working/commonGuide/filters/code/scalaguide/detailed/filters/UserControllerSpec.scala @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package scalaguide.detailed.filters + +// #test-with-withCSRFToken +import org.specs2.mutable.Specification +import play.api.inject.guice.GuiceApplicationBuilder +import play.api.test.CSRFTokenHelper._ +import play.api.test.Helpers._ +import play.api.test.FakeRequest +import play.api.test.WithApplication + +class UserControllerSpec extends Specification { + "UserController GET" should { + + "render the index page from the application" in new WithApplication() { + + val controller = app.injector.instanceOf[UserController] + val request = FakeRequest().withCSRFToken + val result = controller.userGet().apply(request) + + status(result) must beEqualTo(OK) + contentType(result) must beSome("text/html") + } + } +} +// #test-with-withCSRFToken + +// #test-disabling-filters +class UserControllerWithoutFiltersSpec extends Specification { + "UserControllerWithoutFiltersSpec GET" should { + + "render the index page from the application" in new WithApplication( + GuiceApplicationBuilder().configure("play.http.filters" -> "play.api.http.NoHttpFilters").build() + ) { + + val controller = app.injector.instanceOf[UserController] + val request = FakeRequest().withCSRFToken + val result = controller.userGet().apply(request) + + status(result) must beEqualTo(OK) + contentType(result) must beSome("text/html") + } + } +} +// #test-disabling-filters + +import javax.inject.Inject +import play.api.mvc.BaseController +import play.api.mvc.ControllerComponents + +class UserController @Inject()(val controllerComponents: ControllerComponents) extends BaseController { + def userGet = Action { + Ok("success").as(HTML) + } +} diff --git a/manual/working/commonGuide/filters/index.toc b/manual/working/commonGuide/filters/index.toc new file mode 100644 index 00000000..ef336ae1 --- /dev/null +++ b/manual/working/commonGuide/filters/index.toc @@ -0,0 +1,6 @@ +Filters:Play HTTP filters +GzipEncoding:Configuring gzip encoding +SecurityHeaders:Configuring security headers +CorsFilter:Configuring CORS +AllowedHostsFilter:Configuring allowed hosts +RedirectHttpsFilter:Configuring HTTPS redirect diff --git a/manual/working/commonGuide/index.toc b/manual/working/commonGuide/index.toc new file mode 100644 index 00000000..68e71403 --- /dev/null +++ b/manual/working/commonGuide/index.toc @@ -0,0 +1,9 @@ +!build:The build system +!configuration:Configuration +!assets:Static assets +!filters:Built-in HTTP filters +!Modules:Extending Play with modules +!database:Databases +!server:Server Backends +!production:Deploying your application +!schedule:Scheduling tasks diff --git a/manual/detailedTopics/production/ConfiguringHttps.md b/manual/working/commonGuide/production/ConfiguringHttps.md similarity index 85% rename from manual/detailedTopics/production/ConfiguringHttps.md rename to manual/working/commonGuide/production/ConfiguringHttps.md index bc440d06..15eb891f 100644 --- a/manual/detailedTopics/production/ConfiguringHttps.md +++ b/manual/working/commonGuide/production/ConfiguringHttps.md @@ -1,4 +1,4 @@ - + # Configuring HTTPS Play can be configured to serve HTTPS. To enable this, simply tell Play which port to listen to using the `https.port` system property. For example: @@ -15,7 +15,7 @@ HTTPS configuration can either be supplied using system properties or in `applic By default, Play will generate itself a self-signed certificate, however typically this will not be suitable for serving a website. Play uses Java key stores to configure SSL certificates and keys. -Signing authorities often provide instructions on how to create a Java keystore (typically with reference to Tomcat configuration). The official Oracle documentation on how to generate keystores using the JDK keytool utility can be found [here](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html). There is also an example in the [[Generating X.509 Certificates|CertificateGeneration]] section. +Signing authorities often provide instructions on how to create a Java keystore (typically with reference to Tomcat configuration). The official Oracle documentation on how to generate keystores using the JDK keytool utility can be found [here](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html). There is also an example in the [Generating X.509 Certificates](https://lightbend.github.io/ssl-config/CertificateGeneration.html) section. Having created your keystore, the following configuration properties can be used to configure Play to use it: @@ -30,11 +30,11 @@ Another alternative to configure the SSL certificates is to provide a custom [SS #### in Java, an implementation must be provided for [`play.server.SSLEngineProvider`](api/java/play/server/SSLEngineProvider.html) -@[javaexample](code/java/CustomSSLEngineProvider.java) +@[javaexample](code/javaguide/CustomSSLEngineProvider.java) #### in Scala, an implementation must be provided for [`play.server.api.SSLEngineProvider`](api/scala/play/server/api/SSLEngineProvider.html) -@[scalaexample](code/scala/CustomSSLEngineProvider.scala) +@[scalaexample](code/scalaguide/CustomSSLEngineProvider.scala) Having created an implementation for `play.server.SSLEngineProvider` or `play.server.api.SSLEngineProvider`, the following system property configures Play to use it: @@ -53,7 +53,7 @@ To disable binding on the HTTP port, set the `http.port` system property to be ` ## Production usage of HTTPS -If Play is serving HTTPS in production, it should be running JDK 1.8. JDK 1.8 provides a number of new features that make JSSE feasible as a [TLS termination layer](http://blog.ivanristic.com/2014/03/ssl-tls-improvements-in-java-8.html). If not using JDK 1.8, using a [[reverse proxy|HTTPServer]] in front of Play will give better control and security of HTTPS. +If Play is serving HTTPS in production, it should be running JDK 1.8. JDK 1.8 provides a number of new features that make JSSE feasible as a [TLS termination layer](https://blog.ivanristic.com/2014/03/ssl-tls-improvements-in-java-8.html). If not using JDK 1.8, using a [[reverse proxy|HTTPServer]] in front of Play will give better control and security of HTTPS. If you intend to use Play for TLS termination layer, please note the following settings: diff --git a/manual/working/commonGuide/production/Deploying.md b/manual/working/commonGuide/production/Deploying.md new file mode 100644 index 00000000..b478e89f --- /dev/null +++ b/manual/working/commonGuide/production/Deploying.md @@ -0,0 +1,268 @@ + +# Deploying your application + +We have seen how to run a Play application in development mode, however the `run` command should not be used to run an application in production mode. When using `run`, on each request, Play checks with sbt to see if any files have changed, and this may have significant performance impacts on your application. + +There are several ways to deploy a Play application in production mode. Let's start by using the recommended way, creating a distribution artifact. + +## The application secret + +Before you run your application in production mode, you need to generate an application secret. To read more about how to do this, see [[Configuring the application secret|ApplicationSecret]]. In the examples below, you will see the use of `-Dplay.http.secret.key=abcdefghijk`. You must generate your own secret to use here. + +## Deploying Play with JPA + If you are using JPA, you need to take a look at [Deploying with JPA](https://www.playframework.com/documentation/2.6.x/JavaJPA#deploying-play-with-jpa). + +## Using the dist task + +The `dist` task builds a binary version of your application that you can deploy to a server without any dependency on sbt, the only thing the server needs is a Java installation. + +In the Play console, simply type `dist`: + +```bash +[my-first-app] $ dist +``` + +And will see something like: + +```bash +$ sbt +[info] Loading global plugins from /Users/play-developer/.sbt/0.13/plugins +[info] Loading project definition from /Users/play-developer/my-first-app/project +[info] Set current project to my-first-app (in build file:/Users/play-developer/my-first-app/) +[my-first-app] $ dist +[info] Packaging /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT-sources.jar ... +[info] Done packaging. +[info] Wrote /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT.pom +[info] Main Scala API documentation to /Users/play-developer/my-first-app/target/scala-2.11/api... +[info] Packaging /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT-web-assets.jar ... +[info] Done packaging. +[info] Packaging /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT.jar ... +[info] Done packaging. +model contains 21 documentable templates +[info] Main Scala API documentation successful. +[info] Packaging /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT-javadoc.jar ... +[info] Done packaging. +[info] Packaging /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT-sans-externalized.jar ... +[info] Done packaging. +[info] +[info] Your package is ready in /Users/play-developer/my-first-app/target/universal/my-first-app-1.0-SNAPSHOT.zip +[info] +[success] Total time: 5 s, completed Feb 6, 2017 2:08:44 PM +[my-first-app] $ +``` + +This produces a ZIP file containing all JAR files needed to run your application in the `target/universal` folder of your application. + +To run the application, unzip the file on the target server, and then run the script in the `bin` directory. The name of the script is your application name, and it comes in two versions, a bash shell script, and a windows `.bat` script. + +```bash +$ unzip my-first-app-1.0.zip +$ my-first-app-1.0/bin/my-first-app -Dplay.http.secret.key=abcdefghijk +``` + +You can also specify a different configuration file for a production environment, from the command line: + +```bash +$ my-first-app-1.0/bin/my-first-app -Dconfig.file=/full/path/to/conf/application-prod.conf +``` + +For a full description of usage invoke the start script with a `-h` option. + +> For Unix users, zip files do not retain Unix file permissions so when the file is expanded the start script will be required to be set as an executable: +> +> ```bash +> $ chmod +x /path/to/bin/ +> ``` +> +> Alternatively a tar.gz file can be produced instead. Tar files retain permissions. Invoke the `universal:packageZipTarball` task instead of the `dist` task: +> +> ```bash +> sbt universal:packageZipTarball +> ``` + +By default, the `dist` task will include the API documentation in the generated package. If this is not necessary, add these lines in `build.sbt`: + +@[no-scaladoc](code/production.sbt) + +For builds with sub-projects, the statement above has to be applied to all sub-project definitions. + +## The Native Packager + +Play uses the [sbt Native Packager plugin](https://sbt-native-packager.readthedocs.io/en/v1.3.25/). The native packager plugin declares the `dist` task to create a zip file. Invoking the `dist` task is directly equivalent to invoking the following: + +```bash +[my-first-app] $ universal:packageBin +``` + +Many other types of archive can be generated including: + +* tar.gz +* OS X disk images +* Microsoft Installer (MSI) +* RPMs +* Debian packages +* System V / init.d and Upstart services in RPM/Debian packages + +Please consult the [documentation](https://sbt-native-packager.readthedocs.io/en/v1.3.25/) on the native packager plugin for more information. + +### Build a server distribution + +The sbt-native-packager plugins provides a number archetypes. The one that Play uses by default is called the Java server archetype, which enables the following features: + +* System V or Upstart startup scripts +* [Default folders](https://sbt-native-packager.readthedocs.io/en/v1.3.25/archetypes/java_server/index.html#default-mappings) + +More information can be found in the [Java Server Application Archetype documentation](https://sbt-native-packager.readthedocs.io/en/v1.3.25/archetypes/java_server/index.html). + +#### Minimal Debian settings + +Add the following settings to your build: + +@[debian](code/debian.sbt) + +Then build your package with: + +```bash +[my-first-app] $ debian:packageBin +``` + +#### Minimal RPM settings + +Add the following settings to your build: + +@[rpm](code/rpm.sbt) + +Then build your package with: + +```bash +[my-first-app] $ rpm:packageBin +``` + +> There will be some error logging. This is because rpm logs to stderr instead of stdout. + +### Including additional files in your distribution + +Anything included in your project's `dist` directory will be included in the distribution built by the native packager. Note that in Play, the `dist` directory is equivalent to the `src/universal` directory mentioned in the native packager's own documentation. + +## Play PID Configuration + +Play manages its own PID, which is described in the [[Production configuration|ProductionConfiguration]]. + +Since Play uses a separate pidfile, we have to provide it with a proper path, which is `packageName.value` here. The name of the pid file must be `play.pid`. In order to tell the startup script where to place the PID file, put a file `application.ini` inside the `dist/conf` folder and add the following content: + +```bash +s"-Dpidfile.path=/var/run/${packageName.value}/play.pid", +# Add all other startup settings here, too +``` + +Please see the sbt-native-packager [page on Play](https://sbt-native-packager.readthedocs.io/en/v1.3.25/recipes/play.html) for more details. + +To prevent Play from creating a PID just set the property to `/dev/null`: + +```bash +-Dpidfile.path=/dev/null +``` + +For a full list of replacements take a closer look at the [customize java server documentation](https://sbt-native-packager.readthedocs.io/en/v1.3.25/archetypes/java_server/customize.html) and [customize java app documentation](https://sbt-native-packager.readthedocs.io/en/v1.3.25/archetypes/java_app/customize.html). + +## Publishing to a Maven (or Ivy) repository + +You can also publish your application to a Maven repository. This publishes both the JAR file containing your application and the corresponding POM file. + +You have to configure the repository you want to publish to, in your `build.sbt` file: + +@[publish-repo](code/production.sbt) + +Then in the Play console, use the `publish` task: + +```bash +[my-first-app] $ publish +``` + +> Check the [sbt documentation](https://www.scala-sbt.org/release/docs/index.html) to get more information about the resolvers and credentials definition. + +## Running a production server in place + +In some circumstances, you may not want to create a full distribution, you may in fact want to run your application from your project's source directory. This requires an sbt installation on the server, and can be done using the `stage` task. + +```bash +$ sbt clean stage +``` + +And you will see something like this: + +```bash +$ sbt +[info] Loading global plugins from /Users/play-developer/.sbt/0.13/plugins +[info] Loading project definition from /Users/play-developer/my-first-app/project +[info] Set current project to my-first-app (in build file:/Users/play-developer/my-first-app/) +[my-first-app] $ stage +[info] Updating {file:/Users/play-developer/my-first-app/}root... +[info] Packaging /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT-sources.jar ... +[info] Done packaging. +[info] Wrote /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT.pom +[info] Resolving jline#jline;2.12.2 ... +[info] Done updating. +[info] Main Scala API documentation to /Users/play-developer/my-first-app/target/scala-2.11/api... +[info] Compiling 8 Scala sources and 1 Java source to /Users/play-developer/my-first-app/target/scala-2.11/classes... +[info] Packaging /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT-web-assets.jar ... +[info] Done packaging. +model contains 21 documentable templates +[info] Main Scala API documentation successful. +[info] Packaging /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT-javadoc.jar ... +[info] Done packaging. +[info] Packaging /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT.jar ... +[info] Done packaging. +[info] Packaging /Users/play-developer/my-first-app/target/scala-2.11/my-first-app_2.11-1.0-SNAPSHOT-sans-externalized.jar ... +[info] Done packaging. +[success] Total time: 8 s, completed Feb 6, 2017 2:11:10 PM +[my-first-app] $ +``` + +This cleans and compiles your application, retrieves the required dependencies and copies them to the `target/universal/stage` directory. It also creates a `bin/` script where `` is the project's name. The script runs the Play server on Unix style systems and there is also a corresponding `bat` file for Windows. + +For example to start an application of the project `my-first-app` from the project folder you can: + +```bash +$ target/universal/stage/bin/my-first-app -Dplay.http.secret.key=abcdefghijk +``` + +You can also specify a different configuration file for a production environment, from the command line: + +```bash +$ target/universal/stage/bin/my-first-app -Dconfig.file=/full/path/to/conf/application-prod.conf +``` + +### Running a test instance + +Play provides a convenient utility for running a test application in prod mode. + +> **Note:** This is not intended for production usage. + +To run an application in prod mode, run `runProd`: + +```bash +[my-first-app] $ runProd +``` + +## Using the sbt assembly plugin + +Though not officially supported, the sbt assembly plugin may be used to package and run Play applications. This will produce one jar as an output artifact, and allow you to execute it directly using the `java` command. + +To use this, add a dependency on the plugin to your `project/plugins.sbt` file: + +```scala +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") +``` + +Now add the following configuration to your `build.sbt`: + +@[assembly](code/assembly.sbt) + +Now you can build the artifact by running `sbt assembly`, and run your application by running: + +``` +$ java -Dplay.http.secret.key=abcdefghijk -jar target/scala-2.XX/-assembly-.jar +``` + +You'll need to substitute in the right project name, version and scala version, of course. diff --git a/manual/detailedTopics/production/HTTPServer.md b/manual/working/commonGuide/production/HTTPServer.md similarity index 68% rename from manual/detailedTopics/production/HTTPServer.md rename to manual/working/commonGuide/production/HTTPServer.md index 867e8c6e..605e6ff0 100644 --- a/manual/detailedTopics/production/HTTPServer.md +++ b/manual/working/commonGuide/production/HTTPServer.md @@ -1,4 +1,4 @@ - + # Setting up a front end HTTP server You can easily deploy your application as a stand-alone server by setting the application HTTP port to 80: @@ -7,7 +7,7 @@ You can easily deploy your application as a stand-alone server by setting the ap $ /path/to/bin/ -Dhttp.port=80 ``` -> Note that you probably need root permissions to bind a process on this port. +> **Note**: you probably need root permissions to bind a process on this port. However, if you plan to host several applications in the same server or load balance several instances of your application for scalability or fault tolerance, you can use a front end HTTP server. @@ -41,7 +41,7 @@ $HTTP["host"] =~ "www.loadbalancedapp.com" { ## Set up with nginx -This example shows you how to configure [nginx](http://wiki.nginx.org/Main) as a front end web server. Note that you can do the same with Apache, but if you only need virtual hosting or load balancing, nginx is a very good choice and much easier to configure! +This example shows you how to configure [nginx](https://www.nginx.com/resources/wiki/) as a front end web server. Note that you can do the same with Apache, but if you only need virtual hosting or load balancing, nginx is a very good choice and much easier to configure! The `/etc/nginx/nginx.conf` file should define things like this: @@ -103,7 +103,7 @@ http { } ``` -> *Note* Make sure you are using version 1.2 or greater of Nginx otherwise chunked responses won't work properly. +> **Note**: make sure you are using version 1.2 or greater of Nginx otherwise chunked responses won't work properly. ## Set up with Apache @@ -186,7 +186,7 @@ Apache also provides a way to view the status of your cluster. Simply point your Because Play is completely stateless you don’t have to manage sessions between the 2 clusters. You can actually easily scale to more than 2 Play instances. -Note that [Apache does not support Websockets](https://issues.apache.org/bugzilla/show_bug.cgi?id=47485), so you may wish to use another front end proxy (such as [HAProxy](http://www.haproxy.org/) or Nginx) that does implement this functionality. +To use WebSockets, you must use [mod_proxy_wstunnel](http://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html), which was introduced in Apache 2.4. Note that [ProxyPassReverse might rewrite incorrectly headers](https://issues.apache.org/bugzilla/show_bug.cgi?id=51982) adding an extra / to the URIs, so you may wish to use this workaround: ``` @@ -194,21 +194,37 @@ ProxyPassReverse / http://localhost:9999 ProxyPassReverse / http://localhost:9998 ``` -## Configure trusted proxies +## Configuring trusted proxies -To determine the client IP address Play has to know which are the trusted proxies in your network. +Play supports various forwarded headers used by proxies to indicate the incoming IP address and protocol of requests. Play uses this configuration to calculate the correct value for the `remoteAddress` and `secure` fields of `RequestHeader`. -Those can be configured with `play.http.forwarded.trustedProxies`. You can define a list of proxies -and/or subnet masks that Play recognizes as belonging to your network. +It is trivial for an HTTP client, whether it's a browser or other client, to forge forwarded headers, thereby spoofing the IP address and protocol that Play reports, consequently, Play needs to know which proxies are trusted. Play provides a configuration option to configure a list of trusted proxies, and will validate the incoming forwarded headers to verify that they are trusted, taking the first untrusted IP address that it finds as the reported user remote address (or the last IP address if all proxies are trusted.) -Default is `127.0.0.1` and `::FF` +To configure the list of trusted proxies, you can configure `play.http.forwarded.trustedProxies`. This takes a list of IP address or CIDR subnet ranges. Both IPv4 and IPv6 are supported. For example: -There exists two possibilities how proxies are set in the HTTP-headers: +``` +play.http.forwarded.trustedProxies=["192.168.0.0/24", "::1", "127.0.0.1"] +``` + +This says all IP addresses that start with `192.168.0`, as well as the IPv6 and IPv4 loopback addresses, are trusted. By default, Play will just trust the loopback address, that is `::1` and `127.0.0.1`. + +### Trusting all proxies + +Many cloud providers, most notably AWS, provide no guarantees for which IP addresses their load balancer proxies will use. Consequently, the only way to support forwarded headers with these services is to trust all IP addresses. This can be done by configuring the trusted proxies like so: + +``` +play.http.forwarded.trustedProxies=["0.0.0.0/0", "::/0"] +``` + +### Forwarded header version + +Play supports two different versions of forwarded headers: * the legacy method with X-Forwarded headers * the RFC 7239 with Forwarded headers -The type of header to parse is set via `play.http.forwarded.version`. Valid values are `x-forwarded` or `rfc7239`. -The default is `x-forwarded`. +This is configured using `play.http.forwarded.version`, with valid values being `x-forwarded` or `rfc7239`. The default is `x-forwarded`. + +`x-forwarded` uses the de facto standard `X-Forwarded-For` and `X-Forwarded-Proto` headers to determine the correct remote address and protocol for the request. These headers are widely used, however, they have some serious limitations, for example, if you have multiple proxies, and only one of them adds the `X-Forwarded-Proto` header, it's impossible to reliably determine which proxy added it and therefore whether the request from the client was made using https or http. `rfc7239` uses the new `Forwarded` header standard, and solves many of the limitations of the `X-Forwarded-*` headers. -For more information, please read the [RFC 7239](https://tools.ietf.org/html/rfc7239). +For more information, please read the [RFC 7239](https://tools.ietf.org/html/rfc7239) specification. diff --git a/manual/working/commonGuide/production/Production.md b/manual/working/commonGuide/production/Production.md new file mode 100644 index 00000000..df6c7780 --- /dev/null +++ b/manual/working/commonGuide/production/Production.md @@ -0,0 +1,10 @@ + +# Using Play in production + +This section covers topics related to building, configuring and deploying your Play application for production. + +- [[Deploying your application|Deploying]] +- [[Production configuration|ProductionConfiguration]] +- [[Setting up a front end HTTP server|HTTPServer]] +- [[Configuring HTTPS|ConfiguringHttps]] +- [[Deploying to a cloud service|DeployingCloud]] diff --git a/manual/detailedTopics/production/ProductionConfiguration.md b/manual/working/commonGuide/production/ProductionConfiguration.md similarity index 54% rename from manual/detailedTopics/production/ProductionConfiguration.md rename to manual/working/commonGuide/production/ProductionConfiguration.md index 2852a7ea..f54aab74 100644 --- a/manual/detailedTopics/production/ProductionConfiguration.md +++ b/manual/working/commonGuide/production/ProductionConfiguration.md @@ -1,5 +1,5 @@ - -# Additional configuration + +# Production Configuration There are a number of different types of configuration that you can configure in production. The three mains types are: @@ -60,7 +60,7 @@ $ /path/to/bin/ -Dconfig.file=/opt/conf/prod.conf Sometimes you don't want to specify another complete configuration file, but just override a bunch of specific keys. You can do that by specifying then as Java System properties: ``` -$ /path/to/bin/ -Dplay.crypto.secret=abcdefghijk -Ddb.default.password=toto +$ /path/to/bin/ -Dplay.http.secret.key=abcdefghijk -Ddb.default.password=toto ``` #### Specifying the HTTP server address and port using system properties @@ -87,6 +87,12 @@ Using this file, you can stop your application using the `kill` command, for exa $ kill $(cat /var/run/play.pid) ``` +To prevent Play from creating it's own PID, you can set the path to `/dev/null` in your `application.conf` file: + +``` +pidfile.path = "/dev/null" +``` + ### Using environment variables You can also reference environment variables from your `application.conf` file: @@ -100,133 +106,17 @@ Here, the override field `my.key = ${?MY_KEY_ENV}` simply vanishes if there's no ### Server configuration options +Play's default HTTP server implementation is Akka HTTP, and this provides a large number of ways to tune and configure the server, including the size of parser buffers, whether keep alive is used, and so on. + A full list of server configuration options, including defaults, can be seen here: -``` -play { - server { - - # The root directory for the Play server instance. This value can - # be set by providing a path as the first argument to the Play server - # launcher script. See `ServerConfig.loadConfiguration`. - dir = ${?user.dir} - - # HTTP configuration - http { - # The HTTP port of the server. Use a value of "disabled" if the server - # shouldn't bind an HTTP port. - port = 9000 - port = ${?http.port} - - # The interface address to bind to. - address = "0.0.0.0" - address = ${?http.address} - } - - # HTTPS configuration - https { - - # The HTTPS port of the server. - port = ${?https.port} - - # The interface address to bind to - address = "0.0.0.0" - address = ${?https.address} - - # The SSL engine provider - engineProvider = "play.core.server.ssl.DefaultSSLEngineProvider" - engineProvider = ${?play.http.sslengineprovider} - - # HTTPS keystore configuration, used by the default SSL engine provider - keyStore { - # The path to the keystore - path = ${?https.keyStore} - - # The type of the keystore - type = "JKS" - type = ${?https.keyStoreType} - - # The password for the keystore - password = "" - password = ${?https.keyStorePassword} - - # The algorithm to use. If not set, uses the platform default algorithm. - algorithm = ${?https.keyStoreAlgorithm} - } - - # HTTPS truststore configuration - trustStore { - - # If true, does not do CA verification on client side certificates - noCaVerification = false - } - } - - # The type of ServerProvider that should be used to create the server. - # If not provided, the ServerStart class that instantiates the server - # will provide a default value. - provider = ${?server.provider} - - # The path to the process id file created by the server when it runs. - # If set to "/dev/null" then no pid file will be created. - pidfile.path = ${play.server.dir}/RUNNING_PID - pidfile.path = ${?pidfile.path} - - # Configuration options specific to Netty - netty { - # The maximum length of the initial line. This effectively restricts the maximum length of a URL that the server will - # accept, the initial line consists of the method (3-7 characters), the URL, and the HTTP version (8 characters), - # including typical whitespace, the maximum URL length will be this number - 18. - maxInitialLineLength = 4096 - maxInitialLineLength = ${?http.netty.maxInitialLineLength} - - # The maximum length of the HTTP headers. The most common effect of this is a restriction in cookie length, including - # number of cookies and size of cookie values. - maxHeaderSize = 8192 - maxHeaderSize = ${?http.netty.maxHeaderSize} - - # The maximum length of body bytes that Netty will read into memory at a time. - # This is used in many ways. Note that this setting has no relation to HTTP chunked transfer encoding - Netty will - # read "chunks", that is, byte buffers worth of content at a time and pass it to Play, regardless of whether the body - # is using HTTP chunked transfer encoding. A single HTTP chunk could span multiple Netty chunks if it exceeds this. - # A body that is not HTTP chunked will span multiple Netty chunks if it exceeds this or if no content length is - # specified. This only controls the maximum length of the Netty chunk byte buffers. - maxChunkSize = 8192 - maxChunkSize = ${?http.netty.maxChunkSize} - - # Whether the Netty wire should be logged - log.wire = false - log.wire = ${?http.netty.log.wire} - - # Netty options. Possible keys here are defined by: - # - # http://netty.io/3.9/api/org/jboss/netty/channel/socket/SocketChannelConfig.html - # http://netty.io/3.9/api/org/jboss/netty/channel/socket/ServerSocketChannelConfig.html - # http://netty.io/3.9/api/org/jboss/netty/channel/socket/nio/NioSocketChannelConfig.html - # - # Options that pertain to the listening server socket are defined at the top level, options for the sockets associated - # with received client connections are prefixed with child.* - option { - - # Set whether connections should use TCP keep alive - # child.keepAlive = false - - # Set whether the TCP no delay flag is set - # child.tcpNoDelay = false - - # Set the size of the backlog of TCP connections. The default and exact meaning of this parameter is JDK specific. - # backlog = 100 - } - } - } - - # Configuration specific to Play's experimental Akka HTTP backend - akka { - # How long to wait when binding to the listening socket - http-bind-timeout = 5 seconds - } -} -``` +@[](/confs/play-akka-http-server/reference.conf) + +You can also use Netty as the HTTP server, which also provides its own configurations. A full list of Netty server configuration, including the defaults, can be seen below: + +@[](/confs/play-netty-server/reference.conf) + +> **Note**: The Netty server backend is not the default in 2.6.x, and so must be specifically enabled. ## Logging configuration @@ -243,7 +133,7 @@ You can also specify another logback configuration file via a System property. P Specify another logback configuration file to be loaded from the classpath: ``` -$ /path/to/bin/ -Dlogger.resource=conf/prod-logger.xml +$ /path/to/bin/ -Dlogger.resource=prod-logger.xml ``` ### Using `-Dlogger.file` @@ -262,6 +152,8 @@ Specify another logback configuration file to be loaded from an URL: $ /path/to/bin/ -Dlogger.url=http://conf.mycompany.com/logger.xml ``` +> **Note**: To see which file is being used, you can set a system property to debug it: `-Dlogback.debug=true`. + ## JVM configuration You can specify any JVM arguments to the application startup script. Otherwise the default JVM settings will be used: @@ -269,10 +161,3 @@ You can specify any JVM arguments to the application startup script. Otherwise t ``` $ /path/to/bin/ -J-Xms128M -J-Xmx512m -J-server ``` - -As a convenience you can also set memory min, max, permgen and the reserved code cache size in one go; a formula is used to -determine these values given the supplied parameter (which represents maximum memory): - -``` -$ /path/to/bin/ -mem 512 -J-server -``` diff --git a/manual/detailedTopics/production/cloud/Deploying-Boxfuse.md b/manual/working/commonGuide/production/cloud/Deploying-Boxfuse.md similarity index 93% rename from manual/detailedTopics/production/cloud/Deploying-Boxfuse.md rename to manual/working/commonGuide/production/cloud/Deploying-Boxfuse.md index 0cefe3ed..9270c4e2 100644 --- a/manual/detailedTopics/production/cloud/Deploying-Boxfuse.md +++ b/manual/working/commonGuide/production/cloud/Deploying-Boxfuse.md @@ -1,4 +1,4 @@ - + # Deploying to Boxfuse and AWS Boxfuse lets you deploy your Play applications on AWS. It is based on 3 core principles: Immutable Infrastructure, Minimal Images and Blue/Green deployments. @@ -15,7 +15,7 @@ As Boxfuse works with your AWS account, it first needs the necessary permissions ## Build your Application -Package your app using the Typesafe Activator by typing the `activator dist` command in your project directory. +Package your app using the `sbt dist` command in your project directory. ## Deploy your Application @@ -52,4 +52,4 @@ myapp$ boxfuse open -env=prod * [Get Started with Boxfuse & Play](https://boxfuse.com/getstarted/play) * [Deploy Play Framework Scala Apps effortlessly to AWS](https://boxfuse.com/blog/playframework-aws) -* [Boxfuse Play integration reference documentation](https://boxfuse.com/docs/payloads/play) \ No newline at end of file +* [Boxfuse Play integration reference documentation](https://boxfuse.com/docs/payloads/play) diff --git a/manual/detailedTopics/production/cloud/Deploying-CleverCloud.md b/manual/working/commonGuide/production/cloud/Deploying-CleverCloud.md similarity index 94% rename from manual/detailedTopics/production/cloud/Deploying-CleverCloud.md rename to manual/working/commonGuide/production/cloud/Deploying-CleverCloud.md index 7d28a6ad..234e1f03 100644 --- a/manual/detailedTopics/production/cloud/Deploying-CleverCloud.md +++ b/manual/working/commonGuide/production/cloud/Deploying-CleverCloud.md @@ -1,4 +1,4 @@ - + # Deploying to Clever Cloud [Clever Cloud](https://www.clever-cloud.com/en/) is a Platform as a Service solution. You can deploy on it Scala, Java, PHP, Python and Node.js applications. Its main particularity is that it supports **automatic vertical and horizontal scaling**. @@ -37,7 +37,7 @@ You can check the deployment of your application by visiting the ***logs*** sect ## [Optional] Configure your application -You can custom your application with a `clevercloud/play.json` file. +You can custom your application with a `clevercloud/sbt.json` file. The file must contain the following fields: diff --git a/manual/detailedTopics/production/cloud/Deploying-CloudFoundry.md b/manual/working/commonGuide/production/cloud/Deploying-CloudFoundry.md similarity index 98% rename from manual/detailedTopics/production/cloud/Deploying-CloudFoundry.md rename to manual/working/commonGuide/production/cloud/Deploying-CloudFoundry.md index 3b4ec43e..ac0192a6 100644 --- a/manual/detailedTopics/production/cloud/Deploying-CloudFoundry.md +++ b/manual/working/commonGuide/production/cloud/Deploying-CloudFoundry.md @@ -1,4 +1,4 @@ - + # Deploying to CloudFoundry / AppFog ## Prerequisites diff --git a/manual/detailedTopics/production/cloud/DeployingCloud.md b/manual/working/commonGuide/production/cloud/DeployingCloud.md similarity index 81% rename from manual/detailedTopics/production/cloud/DeployingCloud.md rename to manual/working/commonGuide/production/cloud/DeployingCloud.md index b474a317..294e9a3d 100644 --- a/manual/detailedTopics/production/cloud/DeployingCloud.md +++ b/manual/working/commonGuide/production/cloud/DeployingCloud.md @@ -1,4 +1,4 @@ - + # Deploying a Play application to a cloud service Many third party cloud services have built in support for deploying Play applications. diff --git a/manual/detailedTopics/production/cloud/ProductionHeroku.md b/manual/working/commonGuide/production/cloud/ProductionHeroku.md similarity index 87% rename from manual/detailedTopics/production/cloud/ProductionHeroku.md rename to manual/working/commonGuide/production/cloud/ProductionHeroku.md index 39796767..cc3a8b42 100644 --- a/manual/detailedTopics/production/cloud/ProductionHeroku.md +++ b/manual/working/commonGuide/production/cloud/ProductionHeroku.md @@ -1,4 +1,4 @@ - + # Deploying to Heroku [Heroku](https://www.heroku.com/) is a cloud application platform – a way of building and deploying web apps. @@ -98,7 +98,7 @@ $ heroku logs 2015-07-13T20:44:54.960105+00:00 app[web.1]: [info] p.a.l.c.ActorSystemProvider - Starting application default Akka system: application 2015-07-13T20:44:55.066582+00:00 app[web.1]: [info] play.api.Play$ - Application started (Prod) 2015-07-13T20:44:55.445021+00:00 heroku[web.1]: State changed from starting to up -2015-07-13T20:44:55.330940+00:00 app[web.1]: [info] p.c.s.NettyServer$ - Listening for HTTP on /0:0:0:0:0:0:0:0:8626 +2015-07-13T20:44:55.330940+00:00 app[web.1]: [info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000 ... ``` @@ -112,7 +112,7 @@ $ heroku logs -t --app warm-frost-1289 2015-07-13T20:44:54.960105+00:00 app[web.1]: [info] p.a.l.c.ActorSystemProvider - Starting application default Akka system: application 2015-07-13T20:44:55.066582+00:00 app[web.1]: [info] play.api.Play$ - Application started (Prod) 2015-07-13T20:44:55.445021+00:00 heroku[web.1]: State changed from starting to up -2015-07-13T20:44:55.330940+00:00 app[web.1]: [info] p.c.s.NettyServer$ - Listening for HTTP on /0:0:0:0:0:0:0:0:8626 +2015-07-13T20:44:55.330940+00:00 app[web.1]: [info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000 ... ``` @@ -122,16 +122,36 @@ Looks good. We can now visit the app by running: $ heroku open ``` +### Troubleshooting + +If your app contains a `build.gradle` file, Heroku will detect it and try to build your app as Gradle project instead of a Scala sbt project. You can force Heroku to use with sbt by running the following command: + +```bash +$ heroku buildpacks:set heroku/scala +``` + +The [Scala buildpack](https://github.com/heroku/heroku-buildpack-scala) will use the `build.sbt` file in your repo to build the app. + + +## Deploying applications that use latest java versions + +Heroku uses OpenJDK 8 to run Java applications by default. It cannot automatically determine if another version is needed, so deploying an application that uses newer java version leads to a compilation error on the server. If you use a newer version than Java 8, you should declare it in your `system.properties` file in the project root directory, for example: +```txt +java.runtime.version=11 +``` + +See the [heroku documentation](https://devcenter.heroku.com/articles/java-support#specifying-a-java-version) for more details. + ## Deploying with the sbt-heroku plugin The Heroku sbt plugin utilizes an API to provide direct deployment of prepackaged standalone web applications to Heroku. This may be a preferred approach for applications that take a long time to compile, or that need to be deployed from a Continuous Integration server such as Travis CI or Jenkins. ### Adding the plugin -To include the plugin in your project, add the following to your `project/plugins.sbt` file: +To include the [Heroku sbt Plugin](https://github.com/heroku/sbt-heroku) in your project, add the following to your `project/plugins.sbt` file: ```scala -addSbtPlugin("com.heroku" % "sbt-heroku" % "0.5.1") +addSbtPlugin("com.heroku" % "sbt-heroku" % "2.0.0") ``` Next, we must configure the name of the Heroku application the plugin will deploy to. But first, create a new app. Install the Heroku Toolbelt and run the create command. @@ -208,12 +228,12 @@ libraryDependencies += "org.postgresql" % "postgresql" % "9.4-1201-jdbc41" Then create a new file in your project's root directory named `Procfile` (with a capital "P") that contains the following (substituting the `myapp` with your project's name): ```txt -web: target/universal/stage/bin/myapp -Dhttp.port=${PORT} -Dplay.evolutions.db.default.autoApply=true -Ddb.default.url=${DATABASE_URL} +web: target/universal/stage/bin/myapp -Dhttp.port=${PORT} -Dplay.evolutions.db.default.autoApply=true -Ddb.default.driver=org.postgresql.Driver -Ddb.default.url=${DATABASE_URL} ``` This instructs Heroku that for the process named `web` it will run Play and override the `play.evolutions.db.default.autoApply`, `db.default.driver`, and `db.default.url` configuration parameters. Note that the `Procfile` command can be maximum 255 characters long. Alternatively, use the `-Dconfig.resource=` or `-Dconfig.file=` mentioned in [[production configuration|ProductionConfiguration]] page. -Also, be aware the the `DATABASE_URL` is in the platform independent format: +Also, be aware the `DATABASE_URL` is in the platform independent format: ```text vendor://username:password@host:port/db @@ -244,7 +264,6 @@ Note that the creation of a Procfile is not actually required by Heroku, as Hero * [Using WebSockets on Heroku with Java and the Play Framework](https://devcenter.heroku.com/articles/play-java-websockets) * [Seed Project for Play and Heroku](https://github.com/jkutner/play-heroku-seed) * [Play Tutorial for Java](https://github.com/jamesward/play2torial/blob/master/JAVA.md) -* [Getting Started with Play, Scala, and Squeryl](https://www.artima.com/articles/play2_scala_squeryl.html) * [Edge Caching With Play, Heroku, and CloudFront](http://www.jamesward.com/2012/08/08/edge-caching-with-play2-heroku-cloudfront) * [Optimizing Play for Database-Driven Apps](http://www.jamesward.com/2012/06/25/optimizing-play-2-for-database-driven-apps) * [Play App with a Scheduled Job on Heroku](https://github.com/jamesward/play2-scheduled-job-demo) diff --git a/manual/working/commonGuide/production/cloud/index.toc b/manual/working/commonGuide/production/cloud/index.toc new file mode 100644 index 00000000..0eb0a1c8 --- /dev/null +++ b/manual/working/commonGuide/production/cloud/index.toc @@ -0,0 +1,5 @@ +DeployingCloud:Deploying to a cloud service +ProductionHeroku:Deploying to Heroku +Deploying-CloudFoundry:Deploying to Cloud Foundry +Deploying-CleverCloud:Deploying to Clever Cloud +Deploying-Boxfuse:Deploying to Boxfuse and AWS \ No newline at end of file diff --git a/manual/working/commonGuide/production/code/assembly.sbt b/manual/working/commonGuide/production/code/assembly.sbt new file mode 100644 index 00000000..e35bc8b3 --- /dev/null +++ b/manual/working/commonGuide/production/code/assembly.sbt @@ -0,0 +1,22 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + +//#assembly +mainClass in assembly := Some("play.core.server.ProdServerStart") +fullClasspath in assembly += Attributed.blank(PlayKeys.playPackageAssets.value) + +assemblyMergeStrategy in assembly := { + case manifest if manifest.contains("MANIFEST.MF") => + // We don't need manifest files since sbt-assembly will create + // one with the given settings + MergeStrategy.discard + case referenceOverrides if referenceOverrides.contains("reference-overrides.conf") => + // Keep the content for all reference-overrides.conf files + MergeStrategy.concat + case x => + // For all the other files, use the default sbt-assembly merge strategy + val oldStrategy = (assemblyMergeStrategy in assembly).value + oldStrategy(x) +} +//#assembly diff --git a/manual/detailedTopics/production/code/debian.sbt b/manual/working/commonGuide/production/code/debian.sbt similarity index 75% rename from manual/detailedTopics/production/code/debian.sbt rename to manual/working/commonGuide/production/code/debian.sbt index 3af5d7ee..5f2cd7a1 100644 --- a/manual/detailedTopics/production/code/debian.sbt +++ b/manual/working/commonGuide/production/code/debian.sbt @@ -1,3 +1,7 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + //#debian lazy val root = (project in file(".")) .enablePlugins(PlayScala, DebianPlugin) @@ -7,4 +11,4 @@ maintainer in Linux := "First Lastname " packageSummary in Linux := "My custom package summary" packageDescription := "My longer package description" -//#debian \ No newline at end of file +//#debian diff --git a/manual/working/commonGuide/production/code/javaguide/CustomSSLEngineProvider.java b/manual/working/commonGuide/production/code/javaguide/CustomSSLEngineProvider.java new file mode 100644 index 00000000..4a8e7fcb --- /dev/null +++ b/manual/working/commonGuide/production/code/javaguide/CustomSSLEngineProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide; + +// #javaexample +import play.server.ApplicationProvider; +import play.server.SSLEngineProvider; + +import javax.net.ssl.*; +import java.security.NoSuchAlgorithmException; + +public class CustomSSLEngineProvider implements SSLEngineProvider { + private ApplicationProvider applicationProvider; + + public CustomSSLEngineProvider(ApplicationProvider applicationProvider) { + this.applicationProvider = applicationProvider; + } + + @Override + public SSLEngine createSSLEngine() { + try { + // change it to your custom implementation + return SSLContext.getDefault().createSSLEngine(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +} +// #javaexample diff --git a/manual/detailedTopics/production/code/production.sbt b/manual/working/commonGuide/production/code/production.sbt similarity index 51% rename from manual/detailedTopics/production/code/production.sbt rename to manual/working/commonGuide/production/code/production.sbt index 11939a44..69b87ad6 100644 --- a/manual/detailedTopics/production/code/production.sbt +++ b/manual/working/commonGuide/production/code/production.sbt @@ -1,3 +1,7 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + //#no-scaladoc sources in (Compile, doc) := Seq.empty @@ -6,10 +10,13 @@ publishArtifact in (Compile, packageDoc) := false //#publish-repo publishTo := Some( - "My resolver" at "https://mycompany.com/repo" + "My resolver".at("https://mycompany.com/repo") ) credentials += Credentials( - "Repo", "https://mycompany.com/repo", "admin", "admin123" + "Repo", + "https://mycompany.com/repo", + "admin", + "admin123" ) //#publish-repo diff --git a/manual/detailedTopics/production/code/rpm.sbt b/manual/working/commonGuide/production/code/rpm.sbt similarity index 83% rename from manual/detailedTopics/production/code/rpm.sbt rename to manual/working/commonGuide/production/code/rpm.sbt index 91f99fb7..b4de6c57 100644 --- a/manual/detailedTopics/production/code/rpm.sbt +++ b/manual/working/commonGuide/production/code/rpm.sbt @@ -1,3 +1,7 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + //#rpm lazy val root = (project in file(".")) .enablePlugins(PlayScala, RpmPlugin) diff --git a/manual/detailedTopics/production/code/scala/CustomSSLEngineProvider.scala b/manual/working/commonGuide/production/code/scalaguide/CustomSSLEngineProvider.scala similarity index 78% rename from manual/detailedTopics/production/code/scala/CustomSSLEngineProvider.scala rename to manual/working/commonGuide/production/code/scalaguide/CustomSSLEngineProvider.scala index 024eb6c0..58e01766 100644 --- a/manual/detailedTopics/production/code/scala/CustomSSLEngineProvider.scala +++ b/manual/working/commonGuide/production/code/scalaguide/CustomSSLEngineProvider.scala @@ -1,4 +1,7 @@ -package scala +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package scalaguide // #scalaexample import javax.net.ssl._ diff --git a/manual/working/commonGuide/production/index.toc b/manual/working/commonGuide/production/index.toc new file mode 100644 index 00000000..b34283d7 --- /dev/null +++ b/manual/working/commonGuide/production/index.toc @@ -0,0 +1,6 @@ +Production:Using Play in production +Deploying:Deploying your application +ProductionConfiguration:Production configuration +HTTPServer:Setting up a front end HTTP server +ConfiguringHttps:Configuring HTTPS +!cloud:Deploying to a cloud service \ No newline at end of file diff --git a/manual/working/commonGuide/schedule/ScheduledTasks.md b/manual/working/commonGuide/schedule/ScheduledTasks.md new file mode 100644 index 00000000..9503ea08 --- /dev/null +++ b/manual/working/commonGuide/schedule/ScheduledTasks.md @@ -0,0 +1,91 @@ + +# Scheduling asynchronous tasks + +You can schedule sending messages to actors and executing tasks (functions or `Runnable` instances). You will get a `Cancellable` back that you can call `cancel` on to cancel the execution of the scheduled operation. + +For example, to send a message to the `testActor` every 30 seconds: + +Scala +: @[](code/scalaguide/scheduling/MyActorTask.scala) + +Java: +: @[](code/javaguide/scheduling/MyActorTask.java) + +> **Note:** See [[Scala|ScalaAkka#Dependency-injecting-actors]] or [[Java|JavaAkka#Dependency-injecting-actors]] documentation about how to inject actors. + +Similarly, to run a block of code 10 seconds from now, every minute: + +Scala +: @[schedule-block-with-interval](code/scalaguide/scheduling/CodeBlockTask.scala) + +Java +: @[schedule-block-with-interval](code/javaguide/scheduling/CodeBlockTask.java) + +Or to run a block of code once 10 seconds from now: + +Scala +: @[schedule-block-once](code/scalaguide/scheduling/CodeBlockTask.scala) + +Java +: @[](code/javaguide/scheduling/CodeBlockOnceTask.java) + +You can see the Akka documentation to see other possible uses of the scheduler. See the documentation for [`akka.actor.Scheduler` for Scala](https://doc.akka.io/api/akka/2.5/akka/actor/Scheduler.html) or [for Java](https://doc.akka.io/japi/akka/2.5/akka/actor/Scheduler.html). + +> **Note**: Instead of using the default `ExecutionContext`, you can instead create a `CustomExecutionContext`. See documentation for [Java](api/java/play/libs/concurrent/CustomExecutionContext.html) or [Scala](api/scala/play/api/libs/concurrent/CustomExecutionContext.html). See the section about it below. + +## Starting tasks when your app starts + +After defining the tasks as described above, you need to initialize them when your application starts. + +### Using Guice Dependency Injection + +When using Guice Dependency Injection, you will need to create and enable a module to load the tasks as [eager singletons](https://github.com/google/guice/wiki/Scopes#eager-singletons): + +Scala +: @[](code/scalaguide/scheduling/TasksModule.scala) + +Java +: @[](code/javaguide/scheduling/TasksModule.java) + +And then enable the module in your `application.conf` by adding the following line: + +``` +play.modules.enabled += "tasks.TasksModule" +``` + +As the task definitions are completely integrated with the Dependency Injection framework, you can also inject any necessary component inside of them. For more details about how to use Guice Dependency Injection, see [[Scala|ScalaDependencyInjection]] or [[Java|JavaDependencyInjection]] documentation. + +### Using compile-time Dependency Injection + +When using compile-time Dependency Injection, you just need to start them in your implementation of `BuiltInComponents`: + +Scala +: @[](code/scalaguide/scheduling/MyBuiltInComponentsFromContext.scala) + +Java +: @[](code/javaguide/scheduling/MyBuiltInComponentsFromContext.java) + +This must then be used with your custom `ApplicationLoader` implementation. For more details about how to use compile-time Dependency Injection, see [[Scala|ScalaCompileTimeDependencyInjection]] or [[Java|JavaCompileTimeDependencyInjection]] documentation. + +## Using a `CustomExecutionContext` + +You should use a custom execution context when creating tasks that do sync/blocking work. For example, if your task is accessing a database using JDBC, it is doing blocking I/O. If you use the default execution context, your tasks will then block threads that are using to receive and handle requests. To avoid that, you should provide a custom execution context: + +Scala +: @[custom-task-execution-context](code/scalaguide/scheduling/TasksCustomExecutionContext.scala) + +Java +: @[custom-task-execution-context](code/javaguide/scheduling/TasksCustomExecutionContext.java) + + +Configure the thread pool as described in [[thread pools documentation|ThreadPools#Using-other-thread-pools]] using `tasks-dispatcher` as the thread pool name, and then inject it in your tasks: + +Scala +: @[task-using-custom-execution-context](code/scalaguide/scheduling/TasksCustomExecutionContext.scala) + +Java +: @[task-using-custom-execution-context](code/javaguide/scheduling/TasksCustomExecutionContext.java) + +## Use third party modules + +There are also modules that you can use to schedule tasks. Visit our [[module directory|ModuleDirectory#Task-Schedulers]] page to see a list of available modules. diff --git a/manual/working/commonGuide/schedule/code/javaguide/scheduling/CodeBlockOnceTask.java b/manual/working/commonGuide/schedule/code/javaguide/scheduling/CodeBlockOnceTask.java new file mode 100644 index 00000000..c0bcc1c7 --- /dev/null +++ b/manual/working/commonGuide/schedule/code/javaguide/scheduling/CodeBlockOnceTask.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// ###skip: 9 +package javaguide.scheduling; + +import akka.actor.ActorSystem; +import scala.concurrent.ExecutionContext; +import scala.concurrent.duration.Duration; + +import javax.inject.Inject; +import java.util.concurrent.TimeUnit; + +public class CodeBlockOnceTask { + + private final ActorSystem actorSystem; + private final ExecutionContext executionContext; + + @Inject + public CodeBlockOnceTask(ActorSystem actorSystem, ExecutionContext executionContext) { + this.actorSystem = actorSystem; + this.executionContext = executionContext; + + this.initialize(); + } + + private void initialize() { + this.actorSystem + .scheduler() + .scheduleOnce( + Duration.create(10, TimeUnit.SECONDS), // delay + () -> System.out.println("Running just once."), + this.executionContext); + } +} diff --git a/manual/working/commonGuide/schedule/code/javaguide/scheduling/CodeBlockTask.java b/manual/working/commonGuide/schedule/code/javaguide/scheduling/CodeBlockTask.java new file mode 100644 index 00000000..c8df020f --- /dev/null +++ b/manual/working/commonGuide/schedule/code/javaguide/scheduling/CodeBlockTask.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// ###replace: package tasks; +package javaguide.scheduling; + +import akka.actor.ActorSystem; +import scala.concurrent.ExecutionContext; +import scala.concurrent.duration.Duration; + +import javax.inject.Inject; +import java.util.concurrent.TimeUnit; + +// #schedule-block-with-interval +public class CodeBlockTask { + + private final ActorSystem actorSystem; + private final ExecutionContext executionContext; + + @Inject + public CodeBlockTask(ActorSystem actorSystem, ExecutionContext executionContext) { + this.actorSystem = actorSystem; + this.executionContext = executionContext; + + this.initialize(); + } + + private void initialize() { + this.actorSystem + .scheduler() + .schedule( + Duration.create(10, TimeUnit.SECONDS), // initialDelay + Duration.create(1, TimeUnit.MINUTES), // interval + () -> System.out.println("Running block of code"), + this.executionContext); + } +} +// #schedule-block-with-interval diff --git a/manual/working/commonGuide/schedule/code/javaguide/scheduling/MyActorTask.java b/manual/working/commonGuide/schedule/code/javaguide/scheduling/MyActorTask.java new file mode 100644 index 00000000..d41db42b --- /dev/null +++ b/manual/working/commonGuide/schedule/code/javaguide/scheduling/MyActorTask.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// ###replace: package tasks; +package javaguide.scheduling; + +import javax.inject.Named; +import javax.inject.Inject; + +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import scala.concurrent.ExecutionContext; +import scala.concurrent.duration.Duration; + +import java.util.concurrent.TimeUnit; + +public class MyActorTask { + + private final ActorRef someActor; + private final ActorSystem actorSystem; + private final ExecutionContext executionContext; + + @Inject + public MyActorTask( + @Named("some-actor") ActorRef someActor, + ActorSystem actorSystem, + ExecutionContext executionContext) { + this.someActor = someActor; + this.actorSystem = actorSystem; + this.executionContext = executionContext; + + this.initialize(); + } + + private void initialize() { + actorSystem + .scheduler() + .schedule( + Duration.create(0, TimeUnit.SECONDS), // initialDelay + Duration.create(30, TimeUnit.SECONDS), // interval + someActor, + "tick", // message, + executionContext, + ActorRef.noSender()); + } +} diff --git a/manual/working/commonGuide/schedule/code/javaguide/scheduling/MyBuiltInComponentsFromContext.java b/manual/working/commonGuide/schedule/code/javaguide/scheduling/MyBuiltInComponentsFromContext.java new file mode 100644 index 00000000..a0804018 --- /dev/null +++ b/manual/working/commonGuide/schedule/code/javaguide/scheduling/MyBuiltInComponentsFromContext.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// ###replace: package tasks; +package javaguide.scheduling; + +import play.ApplicationLoader; +import play.BuiltInComponentsFromContext; +import play.filters.components.NoHttpFiltersComponents; +import play.routing.Router; + +public class MyBuiltInComponentsFromContext extends BuiltInComponentsFromContext + implements NoHttpFiltersComponents { + + public MyBuiltInComponentsFromContext(ApplicationLoader.Context context) { + super(context); + + this.initialize(); + } + + private void initialize() { + // Task is initialize here + new CodeBlockTask(actorSystem(), executionContext()); + } + + @Override + public Router router() { + return Router.empty(); + } +} diff --git a/manual/working/commonGuide/schedule/code/javaguide/scheduling/TasksCustomExecutionContext.java b/manual/working/commonGuide/schedule/code/javaguide/scheduling/TasksCustomExecutionContext.java new file mode 100644 index 00000000..7f11626b --- /dev/null +++ b/manual/working/commonGuide/schedule/code/javaguide/scheduling/TasksCustomExecutionContext.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// ###replace: package tasks; +package javaguide.scheduling; + +// #custom-task-execution-context +import akka.actor.ActorSystem; +import play.libs.concurrent.CustomExecutionContext; +import scala.concurrent.duration.Duration; + +import javax.inject.Inject; +import java.util.concurrent.TimeUnit; + +public class TasksCustomExecutionContext extends CustomExecutionContext { + + @Inject + public TasksCustomExecutionContext(ActorSystem actorSystem) { + super(actorSystem, "tasks-dispatcher"); + } +} +// #custom-task-execution-context + +// #task-using-custom-execution-context +// ###replace: public class SomeTask +class SomeTask { + + private final ActorSystem actorSystem; + private final TasksCustomExecutionContext executor; + + @Inject + public SomeTask(ActorSystem actorSystem, TasksCustomExecutionContext executor) { + this.actorSystem = actorSystem; + this.executor = executor; + + this.initialize(); + } + + private void initialize() { + this.actorSystem + .scheduler() + .schedule( + Duration.create(10, TimeUnit.SECONDS), // initialDelay + Duration.create(1, TimeUnit.MINUTES), // interval + () -> System.out.println("Running block of code"), + this.executor // using the custom executor + ); + } +} +// #task-using-custom-execution-context diff --git a/manual/working/commonGuide/schedule/code/javaguide/scheduling/TasksModule.java b/manual/working/commonGuide/schedule/code/javaguide/scheduling/TasksModule.java new file mode 100644 index 00000000..e4c21070 --- /dev/null +++ b/manual/working/commonGuide/schedule/code/javaguide/scheduling/TasksModule.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// ###replace: package tasks; +package javaguide.scheduling; + +import com.google.inject.AbstractModule; + +public class TasksModule extends AbstractModule { + + @Override + protected void configure() { + bind(MyActorTask.class).asEagerSingleton(); + } +} diff --git a/manual/working/commonGuide/schedule/code/scalaguide/scheduling/CodeBlockTask.scala b/manual/working/commonGuide/schedule/code/scalaguide/scheduling/CodeBlockTask.scala new file mode 100644 index 00000000..842b5fbb --- /dev/null +++ b/manual/working/commonGuide/schedule/code/scalaguide/scheduling/CodeBlockTask.scala @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package scalaguide.scheduling + +import javax.inject.Inject + +import akka.actor.ActorSystem + +import scala.concurrent.ExecutionContext +import scala.concurrent.duration._ + +//#schedule-block-with-interval +class CodeBlockTask @Inject()(actorSystem: ActorSystem)(implicit executionContext: ExecutionContext) { + + actorSystem.scheduler.schedule(initialDelay = 10.seconds, interval = 1.minute) { + // the block of code that will be executed + print("Executing something...") + } +} +//#schedule-block-with-interval + +//#schedule-block-once +class ScheduleOnceTask @Inject()(actorSystem: ActorSystem)(implicit executionContext: ExecutionContext) { + + actorSystem.scheduler.scheduleOnce(delay = 10.seconds) { + // the block of code that will be executed + print("Executing something...") + } + +} +//#schedule-block-once diff --git a/manual/working/commonGuide/schedule/code/scalaguide/scheduling/MyActorTask.scala b/manual/working/commonGuide/schedule/code/scalaguide/scheduling/MyActorTask.scala new file mode 100644 index 00000000..08a36015 --- /dev/null +++ b/manual/working/commonGuide/schedule/code/scalaguide/scheduling/MyActorTask.scala @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +//###replace: package tasks +package scalaguide.scheduling + +import javax.inject.Inject +import javax.inject.Named + +import akka.actor.ActorRef +import akka.actor.ActorSystem + +import scala.concurrent.ExecutionContext +import scala.concurrent.duration._ + +class MyActorTask @Inject()(actorSystem: ActorSystem, @Named("some-actor") someActor: ActorRef)( + implicit executionContext: ExecutionContext +) { + + actorSystem.scheduler.schedule( + initialDelay = 0.microseconds, + interval = 30.seconds, + receiver = someActor, + message = "tick" + ) + +} diff --git a/manual/working/commonGuide/schedule/code/scalaguide/scheduling/MyBuiltInComponentsFromContext.scala b/manual/working/commonGuide/schedule/code/scalaguide/scheduling/MyBuiltInComponentsFromContext.scala new file mode 100644 index 00000000..3490cb4a --- /dev/null +++ b/manual/working/commonGuide/schedule/code/scalaguide/scheduling/MyBuiltInComponentsFromContext.scala @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +//###replace: package tasks +package scalaguide.scheduling + +import play.api.ApplicationLoader.Context +import play.api.routing.Router +import play.api.BuiltInComponentsFromContext +import play.api.NoHttpFiltersComponents + +class MyBuiltInComponentsFromContext(context: Context) + extends BuiltInComponentsFromContext(context) + with NoHttpFiltersComponents { + + override def router: Router = Router.empty + + // Task is initialize here + initialize() + + private def initialize(): Unit = { + new CodeBlockTask(actorSystem) + } +} diff --git a/manual/working/commonGuide/schedule/code/scalaguide/scheduling/TasksCustomExecutionContext.scala b/manual/working/commonGuide/schedule/code/scalaguide/scheduling/TasksCustomExecutionContext.scala new file mode 100644 index 00000000..52f04e0f --- /dev/null +++ b/manual/working/commonGuide/schedule/code/scalaguide/scheduling/TasksCustomExecutionContext.scala @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +//###replace: package tasks +package scalaguide.scheduling + +import scala.concurrent.duration._ + +//#custom-task-execution-context +import javax.inject.Inject + +import akka.actor.ActorSystem +import play.api.libs.concurrent.CustomExecutionContext + +class TasksCustomExecutionContext @Inject()(actorSystem: ActorSystem) + extends CustomExecutionContext(actorSystem, "tasks-dispatcher") +//#custom-task-execution-context + +//#task-using-custom-execution-context +class SomeTask @Inject()(actorSystem: ActorSystem, executor: TasksCustomExecutionContext) { + + actorSystem.scheduler.schedule(initialDelay = 10.seconds, interval = 1.minute)({ + print("Executing something...") + })(executor) // using the custom execution context + +} +//#task-using-custom-execution-context diff --git a/manual/working/commonGuide/schedule/code/scalaguide/scheduling/TasksModule.scala b/manual/working/commonGuide/schedule/code/scalaguide/scheduling/TasksModule.scala new file mode 100644 index 00000000..f24e351c --- /dev/null +++ b/manual/working/commonGuide/schedule/code/scalaguide/scheduling/TasksModule.scala @@ -0,0 +1,10 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +//###replace: package tasks +package scalaguide.scheduling + +import play.api.inject.SimpleModule +import play.api.inject._ + +class TasksModule extends SimpleModule(bind[MyActorTask].toSelf.eagerly()) diff --git a/manual/working/commonGuide/schedule/index.toc b/manual/working/commonGuide/schedule/index.toc new file mode 100644 index 00000000..960242b7 --- /dev/null +++ b/manual/working/commonGuide/schedule/index.toc @@ -0,0 +1 @@ +ScheduledTasks:Scheduling Recurring Tasks \ No newline at end of file diff --git a/manual/working/commonGuide/server/AkkaHttpServer.md b/manual/working/commonGuide/server/AkkaHttpServer.md new file mode 100644 index 00000000..9350fad2 --- /dev/null +++ b/manual/working/commonGuide/server/AkkaHttpServer.md @@ -0,0 +1,78 @@ + +# Akka HTTP Server Backend + +Play uses the [Akka HTTP](https://doc.akka.io/docs/akka-http/current/index.html) server backend to implement HTTP requests and responses using Akka Streams over the network. Akka HTTP implements a full server stack for HTTP, including full HTTPS support, and has support for HTTP/2. + +The Akka HTTP server backend is the default in Play. You can also use the [[Netty backend|NettyServer]] if you choose. + +## Akka HTTP Implementation + +Play's server backend uses the [low level server API](https://doc.akka.io/docs/akka-http/current/server-side/low-level-api.html?language=scala) to handle Akka's `HttpRequest` and `HttpResponse` classes. + +Play's server backend automatically converts of an Akka `HttpRequest` into a Play HTTP request, so that details of the implementation are under the hood. Play handles all the routing and application logic surrounding the server backend, while still providing the power and reliability of Akka-HTTP for processing requests. + +## Working with Blocking APIs + +Like the rest of Play, Akka HTTP is non-blocking. This means that it uses a small number of threads which it keeps loaded with work at all times. + +This poses a problem when working with blocking APIs such as JDBC or HTTPURLConnection, which cause a thread to wait until data has been returned from a remote system. + +Please configure any work with blocking APIs off the main rendering thread, using a `Future` or `CompletionStage` configured with a `CustomExecutionContext` and using a custom thread pool defined in [[ThreadPools]]. See [[JavaAsync]] and [[ScalaAsync]] for more details. + +## Configuring Akka HTTP + +There are a variety of options that can be configured for the Akka HTTP server. These are given in the [[documentation on configuring Akka HTTP|SettingsAkkaHttp]]. + +## HTTP/2 support (experimental) + +Play's Akka HTTP server also supports HTTP/2. This feature is labeled "experimental" because the API may change in the future, and it has not been thoroughly tested in the wild. However, if you'd like to help Play improve please do test out HTTP/2 support and give us feedback about your experience. + +You also should [[Configure HTTPS|ConfiguringHttps]] on your server before enabling HTTP/2. In general, browsers require TLS to work with HTTP/2, and Play's Akka HTTP server uses ALPN (a TLS extension) to negotiate the protocol with clients that support it. + +To add support for HTTP/2, add the `PlayAkkaHttp2Support` plugin. You can do this in an `enablePlugins` call for your project in `build.sbt`, for example: + +``` +lazy val root = (project in file(".")) + .enablePlugins(PlayScala, PlayAkkaHttp2Support) +``` + +Adding the plugin will do multiple things: + + - It will add the `play-akka-http2-support` module, which provides extra configuration for HTTP/2 and depends on the `akka-http2-support` module. By default HTTP/2 is enabled. It can be disabled by passing the `http2.enabled` system property, e.g. `play "start -Dhttp2.enabled=no"`. + - Configures the [Jetty ALPN agent](https://github.com/jetty-project/jetty-alpn-agent) as a Java agent using [sbt-javaagent](https://github.com/sbt/sbt-javaagent), and automatically adds the `-javaagent` argument for `start`, `stage` and `dist` tasks (i.e. production mode). This adds ALPN support to the JDK, allowing Akka HTTP to negotiate the protocol with the client. It *does not* configure for run mode. In JDK 9 this will not be an issue, since ALPN support is provided by default. + +### Using HTTP/2 in `run` mode + +If you need to use HTTP/2 in dev mode, you need to add a `-javaagent` argument for the Jetty ALPN agent to the Java options used to execute sbt + +``` +export SBT_OPTS="$SBT_OPTS -javaagent:$AGENT" +``` + +where `$AGENT` is the path to your Java agent. If you've already run `sbt stage`, you can find the path to the agent in your `target` directory: + +``` +export AGENT=$(pwd)/$(find target -name 'jetty-alpn-agent-*.jar' | head -1) +``` + +You also may want to write a simple script to run your app with the needed options, as demonstrated the `./play` script in the [play-scala-tls-example](https://github.com/playframework/play-scala-tls-example/blob/2.5.x/play) + +## Manually selecting the Akka HTTP server + +If for some reason you have both the Akka HTTP and the Netty server JARs on your classpath, then Play won't be able to predictably choose a server backend. You'll need to manually select the Akka HTTP server. This can be done by explicitly overriding the `play.server.provider` configuration option and setting it to a value of `play.core.server.AkkaHttpServerProvider`. + +The `play.server.provider` configuration setting can be set in the same way as other configuration options. Different methods of setting configuration are described in the [[configuration file documentation|ConfigFile]]. Several examples of enabling the Akka HTTP server backend are shown below. + +The recommended way to do this is to add the setting to two places. First, to enable Akka HTTP for the sbt `run` task, add the following to your `build.sbt`: + +``` +PlayKeys.devSettings += "play.server.provider" -> "play.core.server.AkkaHttpServerProvider" +``` + +Second, to enable the Akka HTTP backend for when you deploy your application or when you use the sbt `start` task, add the following to your `application.conf` file: + +``` +play.server.provider = play.core.server.AkkaHttpServerProvider +``` + +By adding the setting to both `build.sbt` and `application.conf` you can ensure that the Akka HTTP backend will be used in all cases. \ No newline at end of file diff --git a/manual/working/commonGuide/server/NettyServer.md b/manual/working/commonGuide/server/NettyServer.md new file mode 100644 index 00000000..3274c2f2 --- /dev/null +++ b/manual/working/commonGuide/server/NettyServer.md @@ -0,0 +1,50 @@ + +# Netty Server Backend + +Prior to Play 2.6.x, Play used the Netty server backend as the default. In 2.6.x, the default backend was changed to Akka HTTP, but you can still manually select the Netty backend server in your project. + +## Usage + +To use the Netty server backend you first need to disable the Akka HTTP server and add the Netty server plugin to your project: + +```scala +lazy val root = (project in file(".")) + .enablePlugins(PlayScala, PlayNettyServer) + .disablePlugins(PlayAkkaHttpServer) +``` + +Now Play should automatically select the Netty server for running in dev mode, prod and in tests. + +## Manually selecting the Netty server + +If for some reason you have both the Akka HTTP and the Netty server JARs on your classpath, then Play won't be able to predictably choose a server backend. You'll need to manually select the Netty server. This can be done by explicitly overriding the `play.server.provider` configuration option and setting it to a value of `play.core.server.NettyServerProvider`. + +The `play.server.provider` configuration setting can be set in the same way as other configuration options. Different methods of setting configuration are described in the [[configuration file documentation|ConfigFile]]. Several examples of enabling the Netty server are shown below. + +The recommended way to do this is to add the setting to two places. First, to enable Netty for the sbt `run` task, add the following to your `build.sbt`: + +``` +PlayKeys.devSettings += "play.server.provider" -> "play.core.server.NettyServerProvider" +``` + +Second, to enable the Netty backend for when you deploy your application or when you use the sbt `start` task, add the following to your `application.conf` file: + +``` +play.server.provider = play.core.server.NettyServerProvider +``` + +By adding the setting to both `build.sbt` and `application.conf` you can ensure that the Netty backend will be used in all cases. + +## Verifying that the Netty server is running + +When the Netty server is running the request attribute `RequestAttrKey.Server` with the value `netty` will be set for all requests. The Akka HTTP backend will not set a value for this request attribute. + +Scala +: @[server-request-attribute](code/SomeScalaController.scala) + +Java +: @[server-request-attribute](code/SomeJavaController.java) + +## Configuring Netty + +See the [[SettingsNetty]] page. diff --git a/manual/working/commonGuide/server/Server.md b/manual/working/commonGuide/server/Server.md new file mode 100644 index 00000000..a5a88ae7 --- /dev/null +++ b/manual/working/commonGuide/server/Server.md @@ -0,0 +1,9 @@ + +# Server Backends + +Play comes two configurable server backends, which handle the low level work of processing HTTP requests and responses to and from TCP/IP packets. + +Starting in 2.6.x, the default server backend is the Akka HTTP server backend, based on the [Akka-HTTP](https://doc.akka.io/docs/akka-http/current/) server. Prior to 2.6.x, the server backend is Netty. + +* [[Akka HTTP Server|AkkaHttpServer]] +* [[Netty Server|NettyServer]] diff --git a/manual/working/commonGuide/server/code/SomeJavaController.java b/manual/working/commonGuide/server/code/SomeJavaController.java new file mode 100644 index 00000000..92c1f873 --- /dev/null +++ b/manual/working/commonGuide/server/code/SomeJavaController.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +import play.mvc.Controller; +import play.mvc.Result; +import java.util.Optional; +// #server-request-attribute +import play.api.mvc.request.RequestAttrKey; + +public class SomeJavaController extends Controller { + + public Result index() { + assert (request() + .attrs() + .getOptional(RequestAttrKey.Server().asJava()) + .equals(Optional.of("netty"))); + // ... + // ###skip: 1 + return ok(""); + } +} +// #server-request-attribute diff --git a/manual/working/commonGuide/server/code/SomeScalaController.scala b/manual/working/commonGuide/server/code/SomeScalaController.scala new file mode 100644 index 00000000..b2f4b99c --- /dev/null +++ b/manual/working/commonGuide/server/code/SomeScalaController.scala @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +import javax.inject._ +import play.api.mvc._ +//#server-request-attribute +import play.api.mvc.request.RequestAttrKey + +class SomeScalaController @Inject()(cc: ControllerComponents) extends AbstractController(cc) { + + def index = Action { request => + assert(request.attrs.get(RequestAttrKey.Server) == Option("netty")) + // ... + //###skip: 1 + Ok("") + } + +} +//#server-request-attribute diff --git a/manual/working/commonGuide/server/index.toc b/manual/working/commonGuide/server/index.toc new file mode 100644 index 00000000..8fc6e4bf --- /dev/null +++ b/manual/working/commonGuide/server/index.toc @@ -0,0 +1,3 @@ +Server:Section contents +AkkaHttpServer:Play with Akka HTTP Server +NettyServer:Play with Netty Server diff --git a/manual/working/index.toc b/manual/working/index.toc index 848175a9..da770bbe 100644 --- a/manual/working/index.toc +++ b/manual/working/index.toc @@ -1,2 +1,3 @@ -!scalaGuide:Scala 開発者のための Play -!javaGuide:Java 開発者のための Play +javaGuide:Play for Java developers +scalaGuide:Play for Scala developers +commonGuide:Common topics \ No newline at end of file diff --git a/manual/working/javaGuide/JavaHome.md b/manual/working/javaGuide/JavaHome.md deleted file mode 100644 index 7a2f9cf9..00000000 --- a/manual/working/javaGuide/JavaHome.md +++ /dev/null @@ -1,17 +0,0 @@ - - -# Java 開発者のための Play - - -Play アプリケーションは、`play` パッケージ内にある Java API を使って開発することができます。 - - -> (`play.api.mvc` のような) `play.api` パッケージ内の API は Scala 開発者に予約されています。Java 開発者は `play.mvc` などを参照してください。 - -@toc@ diff --git a/manual/working/javaGuide/advanced/JavaAdvanced.md b/manual/working/javaGuide/advanced/JavaAdvanced.md new file mode 100644 index 00000000..7c794376 --- /dev/null +++ b/manual/working/javaGuide/advanced/JavaAdvanced.md @@ -0,0 +1,6 @@ + +# Advanced topics for Java + +This section describes advanced techniques for writing Play applications in Java. + +@toc@ diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/injected.sbt b/manual/working/javaGuide/advanced/dependencyinjection/code/injected.sbt deleted file mode 100644 index ee1f50e0..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/injected.sbt +++ /dev/null @@ -1,3 +0,0 @@ -//#content -routesGenerator := InjectedRoutesGenerator -//#content \ No newline at end of file diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/CurrentSharePrice.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/CurrentSharePrice.java deleted file mode 100644 index fda6b9fe..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/CurrentSharePrice.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di; - -//#singleton -import javax.inject.*; - -@Singleton -public class CurrentSharePrice { - private volatile int price; - - public void set(int p) { - price = p; - } - - public int get() { - return price; - } -} -//#singleton diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/EnglishHello.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/EnglishHello.java deleted file mode 100644 index 74ae3825..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/EnglishHello.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di; - -//#implemented-by -public class EnglishHello implements Hello { - - public String sayHello(String name) { - return "Hello " + name; - } -} -//#implemented-by diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/GermanHello.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/GermanHello.java deleted file mode 100644 index a6676468..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/GermanHello.java +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di; - -public class GermanHello implements Hello { - @Override - public String sayHello(String name) { - return "Hallo " + name; - } -} diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/Hello.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/Hello.java deleted file mode 100644 index 4fbd6af3..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/Hello.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di; - -//#implemented-by -import com.google.inject.ImplementedBy; - -@ImplementedBy(EnglishHello.class) -public interface Hello { - - String sayHello(String name); -} -//#implemented-by diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/JavaDependencyInjection.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/JavaDependencyInjection.java deleted file mode 100644 index b5273d9e..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/JavaDependencyInjection.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di; - -import play.test.*; -import org.junit.Test; - -import static org.junit.Assert.*; -import static org.hamcrest.CoreMatchers.*; - -public class JavaDependencyInjection extends WithApplication { - - @Test - public void fieldInjection() { - assertNotNull(app.injector().instanceOf(javaguide.advanced.di.field.MyComponent.class)); - } - - @Test - public void constructorInjection() { - assertNotNull(app.injector().instanceOf(javaguide.advanced.di.constructor.MyComponent.class)); - } - - @Test - public void singleton() { - app.injector().instanceOf(CurrentSharePrice.class).set(10); - assertThat(app.injector().instanceOf(CurrentSharePrice.class).get(), equalTo(10)); - } - - @Test - public void cleanup() { - app.injector().instanceOf(MessageQueueConnection.class); - stopPlay(); - assertTrue(MessageQueue.stopped); - } - - @Test - public void implementedBy() { - assertThat(app.injector().instanceOf(Hello.class).sayHello("world"), equalTo("Hello world")); - } -} diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/MessageQueue.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/MessageQueue.java deleted file mode 100644 index bee67c24..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/MessageQueue.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di; - -public class MessageQueue { - public static boolean stopped = false; - - public static MessageQueue connect() { - return new MessageQueue(); - } - - public void stop() { - stopped = true; - } -} diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/MessageQueueConnection.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/MessageQueueConnection.java deleted file mode 100644 index 36caa12e..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/MessageQueueConnection.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di; - -//#cleanup -import javax.inject.*; -import play.inject.ApplicationLifecycle; -import play.libs.F; - -import java.util.concurrent.Callable; - -@Singleton -public class MessageQueueConnection { - private final MessageQueue connection; - - @Inject - public MessageQueueConnection(ApplicationLifecycle lifecycle) { - connection = MessageQueue.connect(); - - lifecycle.addStopHook(() -> { - connection.stop(); - return F.Promise.pure(null); - }); - } - - // ... -} -//#cleanup diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/constructor/MyComponent.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/constructor/MyComponent.java deleted file mode 100644 index 3fec7c86..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/constructor/MyComponent.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di.constructor; - -//#constructor -import javax.inject.*; -import play.libs.ws.*; - -public class MyComponent { - private final WSClient ws; - - @Inject - public MyComponent(WSClient ws) { - this.ws = ws; - } - - // ... -} -//#constructor diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/controllers/Application.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/controllers/Application.java deleted file mode 100644 index 490205a9..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/controllers/Application.java +++ /dev/null @@ -1,9 +0,0 @@ -package javaguide.advanced.di.controllers; - -import play.mvc.*; - -public class Application extends Controller { - public Result index() { - return ok(); - } -} \ No newline at end of file diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/field/MyComponent.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/field/MyComponent.java deleted file mode 100644 index 6091b19c..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/field/MyComponent.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di.field; - -//#field -import javax.inject.*; -import play.libs.ws.*; - -public class MyComponent { - @Inject WSClient ws; - - // ... -} -//#field diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/CustomApplicationLoader.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/CustomApplicationLoader.java deleted file mode 100644 index efbc0e11..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/CustomApplicationLoader.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di.guice; - -//#custom-application-loader -import play.Application; -import play.ApplicationLoader; -import play.Configuration; -import play.inject.guice.GuiceApplicationBuilder; -import play.inject.guice.GuiceApplicationLoader; -import play.libs.Scala; - -public class CustomApplicationLoader extends GuiceApplicationLoader { - - @Override - public GuiceApplicationBuilder builder(ApplicationLoader.Context context) { - Configuration extra = new Configuration("a = 1"); - return initialBuilder - .in(context.environment()) - .loadConfig(extra.withFallback(context.initialConfiguration())) - .overrides(overrides(context)); - } - -} -//#custom-application-loader diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/HelloModule.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/HelloModule.java deleted file mode 100644 index 4cd967df..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/HelloModule.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di.guice; - -import javaguide.advanced.di.*; - -//#guice-module -import com.google.inject.AbstractModule; -import com.google.inject.name.Names; - -public class HelloModule extends AbstractModule { - protected void configure() { - - bind(Hello.class) - .annotatedWith(Names.named("en")) - .to(EnglishHello.class); - - bind(Hello.class) - .annotatedWith(Names.named("de")) - .to(GermanHello.class); - } -} -//#guice-module diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/dynamic/HelloModule.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/dynamic/HelloModule.java deleted file mode 100644 index 4ec2a75a..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/dynamic/HelloModule.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di.guice.configured; - -import javaguide.advanced.di.*; - -//#dynamic-guice-module -import com.google.inject.AbstractModule; -import com.google.inject.ConfigurationException; -import com.google.inject.name.Names; -import play.Configuration; -import play.Environment; - -public class HelloModule extends AbstractModule { - - private final Environment environment; - private final Configuration configuration; - - public HelloModule( - Environment environment, - Configuration configuration) { - this.environment = environment; - this.configuration = configuration; - } - - protected void configure() { - // Expect configuration like: - // hello.en = "myapp.EnglishHello" - // hello.de = "myapp.GermanHello" - Configuration helloConf = configuration.getConfig("hello"); - // Iterate through all the languages and bind the - // class associated with that language. Use Play's - // ClassLoader to load the classes. - for (String l: helloConf.subKeys()) { - try { - String bindingClassName = helloConf.getString(l); - Class bindingClass = - environment.classLoader().loadClass(bindingClassName) - .asSubclass(Hello.class); - bind(Hello.class) - .annotatedWith(Names.named(l)) - .to(bindingClass); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - } -} -//#dynamic-guice-module diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/eager/HelloModule.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/eager/HelloModule.java deleted file mode 100644 index 75ccba7a..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/guice/eager/HelloModule.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di.guice.eager; - -import javaguide.advanced.di.*; - -//#eager-guice-module -import com.google.inject.AbstractModule; -import com.google.inject.name.Names; - -public class HelloModule extends AbstractModule { - protected void configure() { - - bind(Hello.class) - .annotatedWith(Names.named("en")) - .to(EnglishHello.class) - .asEagerSingleton(); - - bind(Hello.class) - .annotatedWith(Names.named("de")) - .to(GermanHello.class) - .asEagerSingleton(); - } -} -//#eager-guice-module diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/playlib/HelloModule.java b/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/playlib/HelloModule.java deleted file mode 100644 index fd9a65b0..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide/advanced/di/playlib/HelloModule.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.di.playlib; - -import javaguide.advanced.di.*; - -//#play-module -import play.api.*; -import play.api.inject.*; -import scala.collection.Seq; - -public class HelloModule extends Module { - @Override - public Seq> bindings(Environment environment, Configuration configuration) { - return seq( - bind(Hello.class).qualifiedWith("en").to(EnglishHello.class), - bind(Hello.class).qualifiedWith("de").to(GermanHello.class) - ); - } -} -//#play-module diff --git a/manual/working/javaGuide/advanced/dependencyinjection/index.toc b/manual/working/javaGuide/advanced/dependencyinjection/index.toc deleted file mode 100644 index 92f35dbe..00000000 --- a/manual/working/javaGuide/advanced/dependencyinjection/index.toc +++ /dev/null @@ -1 +0,0 @@ -JavaDependencyInjection:Guice による依存性の注入 \ No newline at end of file diff --git a/manual/working/javaGuide/advanced/embedding/JavaEmbeddingPlay.md b/manual/working/javaGuide/advanced/embedding/JavaEmbeddingPlay.md index 59908b9f..1b69ecbc 100644 --- a/manual/working/javaGuide/advanced/embedding/JavaEmbeddingPlay.md +++ b/manual/working/javaGuide/advanced/embedding/JavaEmbeddingPlay.md @@ -1,4 +1,4 @@ - + # Embedding a Play server in your application While Play apps are most commonly used as their own container, you can also embed a Play server into your own existing application. This can be used in conjunction with the Twirl template compiler and Play routes compiler, but these are of course not necessary, a common use case for embedding a Play application will be because you only have a few very simple routes. @@ -23,4 +23,4 @@ To stop the server once you've started it, simply call the `stop` method: @[stop](code/javaguide/advanced/embedding/JavaEmbeddingPlay.java) -> **Note:** Play requires an application secret to be configured in order to start. This can be configured by providing an `application.conf` file in your application, or using the `play.crypto.secret` system property. \ No newline at end of file +> **Note:** Play requires an application secret to be configured in order to start. This can be configured by providing an `application.conf` file in your application, or using the `play.http.secret.key` system property. diff --git a/manual/working/javaGuide/advanced/embedding/code/javaguide/advanced/embedding/JavaEmbeddingPlay.java b/manual/working/javaGuide/advanced/embedding/code/javaguide/advanced/embedding/JavaEmbeddingPlay.java index 2bcbffb7..b008bf4d 100644 --- a/manual/working/javaGuide/advanced/embedding/code/javaguide/advanced/embedding/JavaEmbeddingPlay.java +++ b/manual/working/javaGuide/advanced/embedding/code/javaguide/advanced/embedding/JavaEmbeddingPlay.java @@ -1,81 +1,99 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.advanced.embedding; -import com.ning.http.client.AsyncHttpClientConfig; +import java.io.IOException; + import org.junit.Test; -import play.libs.F; import play.libs.ws.WSClient; import play.libs.ws.WSResponse; -import play.libs.ws.ning.NingWSClient; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.CompletionStage; import java.util.function.Consumer; -//#imports -import play.Mode; +// #imports import play.routing.RoutingDsl; import play.server.Server; import static play.mvc.Controller.*; -//#imports +// #imports import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; public class JavaEmbeddingPlay { - @Test - public void simple() { - //#simple - Server server = Server.forRouter(new RoutingDsl() - .GET("/hello/:to").routeTo(to -> - ok("Hello " + to) - ) - .build() - ); - //#simple + @Test + public void simple() throws Exception { + // #simple + Server server = + Server.forRouter( + (components) -> + RoutingDsl.fromComponents(components) + .GET("/hello/:to") + .routeTo(to -> ok("Hello " + to)) + .build()); + // #simple - try { - withClient(ws -> { - //#http-port - F.Promise response = ws.url( - "http://localhost:" + server.httpPort() + "/hello/world" - ).get(); - //#http-port - assertThat(response.get(10000).getBody(), equalTo("Hello world")); - }); - } finally { - //#stop - server.stop(); - //#stop - } + try { + withClient( + ws -> { + // #http-port + CompletionStage response = + ws.url("http://localhost:" + server.httpPort() + "/hello/world").get(); + // #http-port + try { + assertThat( + response.toCompletableFuture().get(10, TimeUnit.SECONDS).getBody(), + equalTo("Hello world")); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } finally { + // #stop + server.stop(); + // #stop } + } - @Test - public void config() { - //#config - Server server = Server.forRouter(new RoutingDsl() - .GET("/hello/:to").routeTo(to -> - ok("Hello " + to) - ) - .build(), - Mode.TEST, 19000 - ); - //#config + @Test + public void config() throws Exception { + // #config + Server server = + Server.forRouter( + (components) -> + RoutingDsl.fromComponents(components) + .GET("/hello/:to") + .routeTo(to -> ok("Hello " + to)) + .build()); + // #config - try { - withClient(ws -> - assertThat(ws.url("http://localhost:19000/hello/world").get().get(10000).getBody(), equalTo("Hello world")) - ); - } finally { - server.stop(); - } + try { + withClient( + ws -> { + try { + assertThat( + ws.url("http://localhost:" + server.httpPort() + "/hello/world") + .get() + .toCompletableFuture() + .get(10, TimeUnit.SECONDS) + .getBody(), + equalTo("Hello world")); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } finally { + server.stop(); } + } - private void withClient(Consumer callback) { - try (WSClient client = new NingWSClient(new AsyncHttpClientConfig.Builder().build())) { - callback.accept(client); - } + private void withClient(Consumer callback) throws IOException { + try (WSClient client = play.test.WSTestClient.newClient(19000)) { + callback.accept(client); } - + } } diff --git a/manual/working/javaGuide/advanced/embedding/index.toc b/manual/working/javaGuide/advanced/embedding/index.toc index 97eec16c..867d1608 100644 --- a/manual/working/javaGuide/advanced/embedding/index.toc +++ b/manual/working/javaGuide/advanced/embedding/index.toc @@ -1 +1 @@ -JavaEmbeddingPlay:Play の組み込み \ No newline at end of file +JavaEmbeddingPlay:Embedding Play \ No newline at end of file diff --git a/manual/working/javaGuide/advanced/extending/JavaPlayModules.md b/manual/working/javaGuide/advanced/extending/JavaPlayModules.md new file mode 100644 index 00000000..fd1f9272 --- /dev/null +++ b/manual/working/javaGuide/advanced/extending/JavaPlayModules.md @@ -0,0 +1,57 @@ + +# Writing Play Modules + +> **Note:** This page covers the new [[module system|JavaDependencyInjection#Play-libraries]] to add new functionality to Play. +> +> The deprecated `play.Plugin` system is removed as of 2.5.x. + +A [[module|JavaDependencyInjection#Play-libraries]] can be written using any dependency injection framework. However, when you want to extend Play, you need to avoid dependencies on a specific framework so that your extension can work independently of the dependency injection. Play previously used the `play.Plugin` system for this purpose, but in 2.5.x, Plugins have been replaced with Play modules. + + A Play module is a class that extends [`play.api.inject.Module`](api/scala/play/api/inject/Module.html) and can be registered with Play without relying explicitly on a particular dependency injection framework. This allows everyone to use Play modules. + +A list of Play modules are available in the [[module directory|ModuleDirectory]]. + +In addition, because Play uses Play modules for built-in functionality, a Play module must be used to replace or augment built-in functionality. + +## Creating and migrating Play modules + +Creating a Play module is simple: + +@[module-class-api](code/javaguide/advanced/extending/MyApi.java) + +@[module-class-definition](code/javaguide/advanced/extending/MyModule.java) + +For more information, see the "Create a Module class" section of [[Plugins to Modules|PluginsToModules#Create-a-Module-class]]. + +## Module registration + +By default, Play will load any class called `Module` that is defined in the root package (the "app" directory) or +you can define them explicitly inside the `reference.conf` or the `application.conf`: + +``` +play.modules.enabled += "modules.MyModule" +``` + +Please see [[migration page|PluginsToModules#Wire-it-up]] and [[the dependency injection documentation|JavaDependencyInjection#Play-libraries]] for more details. + +## Application lifecycle + +A module can detect when Play shutdown occurs by injecting the [`play.inject.ApplicationLifecycle`](api/java/play/inject/ApplicationLifecycle.html) interface into the singleton instance and adding a shutdown hook. + +Please see the [[`ApplicationLifecycle` example|PluginsToModules#Create-a-Module-class]] and [[ApplicationLifecycle reference|JavaDependencyInjection#Stopping/cleaning-up]] for more details. + +## Testing Play modules + +Play Modules can be tested using Play's built in test functionality, by using the [`GuiceApplicationBuilder`](api/java/play/inject/guice/GuiceApplicationBuilder.html) and adding a binding to the module. + +@[module-class-binding](code/javaguide/advanced/extending/JavaExtendingPlay.java) + +Please see [[Testing with Guice|JavaTestingWithGuice#Bindings-and-Modules]] for more details. + +## Listing existing Play modules + +The [`Modules.locate(env, conf)`](api/scala/play/api/inject/Modules$.html) method will display a list of all available Play modules in an application. There is no corresponding Java class. + +## Overriding built-in modules + +There are some cases where Play provides a built-in module that must be overridden. In these cases, the Java implementation is a wrapper over the Scala one, and so it's usually more convenient to override the Scala implementation directly. diff --git a/manual/working/javaGuide/advanced/extending/JavaPlugins.md b/manual/working/javaGuide/advanced/extending/JavaPlugins.md index f412e0cb..2b6ef953 100644 --- a/manual/working/javaGuide/advanced/extending/JavaPlugins.md +++ b/manual/working/javaGuide/advanced/extending/JavaPlugins.md @@ -1,54 +1,6 @@ - + # Writing Plugins -In the context of the Play runtime, a plugin is a class that is able to plug into the Play lifecycle, and also allows sharing components in a non static way in your application. - -Not every library that adds functionality to Play is or needs to be a plugin in this context - a library that provides a custom filter for example does not need to be a plugin. - -Similarly, plugins don't necessarily imply that they are reusable between applications, it is often very useful to implement a plugin locally within an application, in order to hook into the Play lifecycle and share components in your code. - -## Implementing plugins - -Implementing a plugin requires two steps. The first is to implement the `play.Plugin` interface: - -@[code](code/javaguide/advanced/extending/MyPlugin.java) - -The next step is to register this with Play. This can be done by creating a file called `play.plugins` and placing it in the root of the classloader. In a typical Play app, this means putting it in the `conf` folder: - -``` -2000:plugins.MyPlugin -``` - -Each line in the `play.plugins` file contains a number followed by the fully qualified name of the plugin to load. The number is used to control lifecycle ordering, lower numbers will be started first and stopped last. Multiple plugins can be declared in the one file, and any lines started with `#` are treated as comments. - -Choosing the right number for ordering for a plugin is important, it needs to fit in appropriate according to what other plugins it depends on. The plugins that Play uses use the following ordering numbers: - -* *100* - Utilities that have no dependencies, such as the messages plugin -* *200* - Database connection pools -* *300-500* - Plugins that depend on the database, such as JPA, ebean and evolutions -* *600* - The Play cache plugin -* *700* - The WS plugin -* *1000* - The Akka plugin -* *10000* - The Global plugin, which invokes the `Global.onStart` and `Global.onStop` methods. This plugin is intended to execute last. - -## Accessing plugins - -Plugins can be accessed via the `plugin` method on `play.Application`: - -@[access-plugin](code/javaguide/advanced/extending/JavaPlugins.java) - -## Actor example - -A common use case for using plugins is to create and share actors around the application. This can be done by implementing an actors plugin: - -@[code](code/javaguide/advanced/extending/Actors.java) - -Note the static methods that allow easy access to the `ActorRef` for each actor, instead of code having to use the plugins API directly. - -The plugin can then be registered in `play.plugins`: - -``` -1100:actors.Actors -``` - -The reason `1100` was chosen for the ordering was because this plugin depends on the Akka plugin, and so must start after that. +> **Note:** The `play.Plugin` API was deprecated in 2.4.x and is removed as of 2.5.x. +> +> Please use [[Play Modules|JavaPlayModules]], and see the [[Plugins to Modules|PluginsToModules]] page for migration details. diff --git a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/Actors.java b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/Actors.java deleted file mode 100644 index fa4f7933..00000000 --- a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/Actors.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -//#code -//###replace: package actors; -package javaguide.advanced.extending; - -import akka.actor.*; -import play.*; -import play.libs.Akka; - -import javax.inject.Inject; - -public class Actors extends Plugin { - private final Application app; - - private ActorRef myActor; - - @Inject - public Actors(Application app) { - this.app = app; - } - - public void onStart() { - myActor = Akka.system().actorOf(MyActor.props(), "my-actor"); - } - - public static ActorRef getMyActor() { - return Play.application().plugin(Actors.class).myActor; - } -} -//#code diff --git a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/JavaExtendingPlay.java b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/JavaExtendingPlay.java new file mode 100644 index 00000000..1ee7bc01 --- /dev/null +++ b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/JavaExtendingPlay.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.advanced.extending; + +import org.junit.Test; + +import play.Application; +import play.inject.guice.GuiceApplicationBuilder; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +public class JavaExtendingPlay { + + @Test + public void testModule() throws Exception { + // #module-class-binding + Application application = new GuiceApplicationBuilder().bindings(new MyModule()).build(); + // #module-class-binding + MyApi api = application.injector().instanceOf(MyApi.class); + + assertNotNull(api); + } +} diff --git a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/JavaPlugins.java b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/JavaPlugins.java deleted file mode 100644 index ff9598c8..00000000 --- a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/JavaPlugins.java +++ /dev/null @@ -1,47 +0,0 @@ -package javaguide.advanced.extending; - -import akka.actor.ActorRef; -import org.junit.Test; -import play.Application; -import play.Play; -import play.libs.F; -import play.test.*; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.concurrent.atomic.AtomicReference; - -import static org.junit.Assert.*; -import static play.test.Helpers.*; -import static akka.pattern.Patterns.ask; - -public class JavaPlugins { - - @Test - public void pluginsShouldBeAccessible() { - final AtomicReference myComponentRef = new AtomicReference(); - Application app = fakeApplication(new HashMap(), Arrays.asList(MyPlugin.class.getName())); - running(app, new Runnable() { - public void run() { - //#access-plugin - MyComponent myComponent = Play.application().plugin(MyPlugin.class).getMyComponent(); - //#access-plugin - assertTrue(myComponent.started); - myComponentRef.set(myComponent); - } - }); - assertTrue(myComponentRef.get().stopped); - } - - @Test - public void actorExampleShouldWork() { - Application app = fakeApplication(new HashMap(), Arrays.asList(Actors.class.getName())); - running(app, new Runnable() { - public void run() { - ActorRef actor = Actors.getMyActor(); - assertEquals("hi", F.Promise.wrap(ask(actor, "hi", 20000)).get(20000)); - } - }); - } - -} diff --git a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyActor.java b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyActor.java deleted file mode 100644 index cc0459de..00000000 --- a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyActor.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.extending; - -import akka.actor.*; - -public class MyActor extends UntypedActor { - public static Props props() { - return Props.create(MyActor.class); - } - - public void onReceive(Object message) throws Exception { - sender().tell(message, self()); - } -} diff --git a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyApi.java b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyApi.java new file mode 100644 index 00000000..5f671dca --- /dev/null +++ b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyApi.java @@ -0,0 +1,9 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.advanced.extending; + +// #module-class-api +public class MyApi {} + +// #module-class-api diff --git a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyComponent.java b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyComponent.java deleted file mode 100644 index bb8a713e..00000000 --- a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyComponent.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -package javaguide.advanced.extending; - -public class MyComponent { - public boolean started; - public boolean stopped; - public void start() { - started = true; - } - public void stop() { - stopped = true; - } -} diff --git a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyModule.java b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyModule.java new file mode 100644 index 00000000..b76309d6 --- /dev/null +++ b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyModule.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.advanced.extending; + +import play.api.Configuration; +import play.api.Environment; +import play.api.inject.Binding; +import scala.collection.Seq; + +// #module-class-definition +public class MyModule extends play.api.inject.Module { + public Seq> bindings(Environment environment, Configuration configuration) { + return seq(bind(MyApi.class).toSelf()); + } +} +// #module-class-definition diff --git a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyPlugin.java b/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyPlugin.java deleted file mode 100644 index 2ffd7396..00000000 --- a/manual/working/javaGuide/advanced/extending/code/javaguide/advanced/extending/MyPlugin.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ -//#code -//###replace: package plugins; -package javaguide.advanced.extending; - -import play.Plugin; - -public class MyPlugin extends Plugin { - - private final MyComponent myComponent = new MyComponent(); - - public void onStart() { - myComponent.start(); - } - - public void onStop() { - myComponent.stop(); - } - - public boolean enabled() { - return true; - } - - public MyComponent getMyComponent() { - return myComponent; - } -} -//#code diff --git a/manual/working/javaGuide/advanced/extending/index.toc b/manual/working/javaGuide/advanced/extending/index.toc index b8bdcde5..63259ee9 100644 --- a/manual/working/javaGuide/advanced/extending/index.toc +++ b/manual/working/javaGuide/advanced/extending/index.toc @@ -1 +1,2 @@ -JavaPlugins:プラグインの書き方 \ No newline at end of file +JavaPlayModules:Playモジュールを作成する +JavaPlugins:プラグインAPIからの移行 diff --git a/manual/working/javaGuide/advanced/http/JavaHttpFilters.md b/manual/working/javaGuide/advanced/http/JavaHttpFilters.md deleted file mode 100644 index 9338984f..00000000 --- a/manual/working/javaGuide/advanced/http/JavaHttpFilters.md +++ /dev/null @@ -1,38 +0,0 @@ - -# Filters - -Play provides a simple filter API for applying global filters to each request. - -## Filters vs action composition - -The filter API is intended for cross cutting concerns that are applied indiscriminately to all routes. For example, here are some common use cases for filters: - -* Logging/metrics collection -* [[GZIP encoding|GzipEncoding]] -* [[Security headers|SecurityHeaders]] - -In contrast, [[action composition|JavaActionsComposition]] is intended for route specific concerns, such as authentication and authorisation, caching and so on. If your filter is not one that you want applied to every route, consider using action composition instead, it is far more powerful. And don't forget that you can create your own action builders that compose your own custom defined sets of actions to each route, to minimise boilerplate. - -## Using filters - -The simplest way to use a filter is to provide an implementation of the [`HttpFilters`](api/java/play/http/HttpFilters.html) interface in the root package called `Filters`: - -@[filters](code/javaguide/httpfilters/Filters.java) - -If you want to have different filters in different environments, or would prefer not putting this class in the root package, you can configure where Play should find the class by setting `play.http.filters` in `application.conf` to the fully qualified class name of the class. For example: - - play.http.filters=com.example.Filters - -## Where do filters fit in? - -Filters wrap the action after the action has been looked up by the router. This means you cannot use a filter to transform a path, method or query parameter to impact the router. However you can direct the request to a different action by invoking that action directly from the filter, though be aware that this will bypass the rest of the filter chain. If you do need to modify the request before the router is invoked, a better way to do this would be to place your logic in `Global.onRouteRequest` instead. - -Since filters are applied after routing is done, it is possible to access routing information from the request, via the `tags` map on the `RequestHeader`. For example, you might want to log the time against the action method. In that case, you might update the `logTime` method to look like this: - -> Routing tags are a feature of the Play router. If you use a custom router, or return a custom action in `Global.onRouteRequest`, these parameters may not be available. - -## Creating a filter - -Creating a filter using Java is currently not ideal because you will need to work with Scala types such as `Iteratee`, which have been originally designed to be consumed from Scala code, and can be quite cumbersome to use in Java. Our reccomendation is to create your custom filters using Scala, and then use them from Java as explained in [[this section|JavaHttpFilters#Using-filters]]. - -Read [[here|ScalaHttpFilters]] to learn how to create a custom filter in Scala. \ No newline at end of file diff --git a/manual/working/javaGuide/advanced/http/JavaHttpRequestHandlers.md b/manual/working/javaGuide/advanced/http/JavaHttpRequestHandlers.md deleted file mode 100644 index 23a0cdd7..00000000 --- a/manual/working/javaGuide/advanced/http/JavaHttpRequestHandlers.md +++ /dev/null @@ -1,31 +0,0 @@ - -# HTTP Request Handlers - -Play abstraction for handling requests is less sophisticated in the Play Java API, when compared to its Scala counterpart. However, it may still be enough if you only need to execute some code before the controller's action method associated to the passed request is executed. If that's not enough, then you should fall back to the [[Scala API|ScalaHttpRequestHandlers]] for creating a HTTP request handler. - -## Implementing a custom request handler - -The [`HttpRequestHandler`](api/java/play/http/HttpRequestHandler.html) interface has two methods that needs to be implemented: - -* `createAction`: Takes the request and the controller's action method associated with the passed request. -* `wrapAction`: Takes the action to be run and allows for a final global interceptor to be added to the action. - -There is also a [`DefaultHttpRequestHandler`](api/java/play/http/DefaultHttpRequestHandler.html) class that can be used if you don't want to implement both methods. - ->> Note: If you are providing an implementation of `wrapAction` because you need to apply a cross cutting concern to an action before is executed, creating a [[filter|JavaHttpFilters]] is a more idiomatic way of achieving the same. - -## Configuring the http request handler - -A custom http handler can be supplied by creating a class in the root package called `RequestHandler` that implements `HttpRequestHandler`, for example: - -@[default](code/javaguide/RequestHandler.java) - -If you don’t want to place your request handler in the root package, or if you want to be able to configure different request handlers for different environments, you can do this by configuring the `play.http.requestHandler` configuration property in `application.conf`: - - play.http.requestHandler = "com.example.RequestHandler" - -### Performance notes - -The http request handler that Play uses if none is configured is one that delegates to the legacy `GlobalSettings` methods. This may have a performance impact as it will mean your application has to do many lookups out of Guice to handle a single request. If you are not using a `Global` object, then you don't need this, instead you can configure Play to use the default http request handler: - - play.http.requestHandler = "play.http.DefaultHttpRequestHandler" diff --git a/manual/working/javaGuide/advanced/http/code/javaguide/RequestHandler.java b/manual/working/javaGuide/advanced/http/code/javaguide/RequestHandler.java deleted file mode 100644 index 724030ec..00000000 --- a/manual/working/javaGuide/advanced/http/code/javaguide/RequestHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2009-2015 Typesafe Inc. - */ - -//#default -import play.http.HttpRequestHandler; -import play.libs.F; -import play.mvc.Action; -import play.mvc.Http; -import play.mvc.Result; - -import java.lang.reflect.Method; - -public class RequestHandler implements HttpRequestHandler { - - @Override - public Action createAction(Http.Request request, Method actionMethod) { - return new Action.Simple() { - @Override - public F.Promise call(Http.Context ctx) throws Throwable { - return delegate.call(ctx); - } - }; - } - - @Override - public Action wrapAction(Action action) { - return action; - } -} -//#default diff --git a/manual/working/javaGuide/advanced/http/code/javaguide/httpfilters/Filters.java b/manual/working/javaGuide/advanced/http/code/javaguide/httpfilters/Filters.java deleted file mode 100644 index 64135119..00000000 --- a/manual/working/javaGuide/advanced/http/code/javaguide/httpfilters/Filters.java +++ /dev/null @@ -1,24 +0,0 @@ -package httpfilters; - -// #filters -import play.api.Logger; -import play.api.mvc.EssentialFilter; -import play.http.HttpFilters; -import play.filters.gzip.GzipFilter; -import javax.inject.Inject; - -public class Filters implements HttpFilters { - - private final GzipFilter gzip; - - @Inject - public Filters(GzipFilter gzip) { - this.gzip = gzip; - } - - @Override - public EssentialFilter[] filters() { - return new EssentialFilter[] { gzip }; - } -} -//#filters diff --git a/manual/working/javaGuide/advanced/http/index.toc b/manual/working/javaGuide/advanced/http/index.toc deleted file mode 100644 index 703d7d86..00000000 --- a/manual/working/javaGuide/advanced/http/index.toc +++ /dev/null @@ -1,2 +0,0 @@ -JavaHttpFilters:HTTP フィルタ -JavaHttpRequestHandlers:HTTP リクエストの処理 \ No newline at end of file diff --git a/manual/working/javaGuide/advanced/index.toc b/manual/working/javaGuide/advanced/index.toc index 0e2f7fdf..5090fa91 100644 --- a/manual/working/javaGuide/advanced/index.toc +++ b/manual/working/javaGuide/advanced/index.toc @@ -1,5 +1,4 @@ -dependencyinjection:依存性の注入 -http:HTTP アーキテクチャ -routing:拡張ルーティング -extending:Play の拡張 -embedding:Play の組み込み \ No newline at end of file +JavaAdvanced:Section contents +routing:高度なルーティング +extending:Playの拡張 +embedding:Playの組み込み diff --git a/manual/working/javaGuide/advanced/routing/JavaJavascriptRouter.md b/manual/working/javaGuide/advanced/routing/JavaJavascriptRouter.md new file mode 100644 index 00000000..a868f733 --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/JavaJavascriptRouter.md @@ -0,0 +1,75 @@ + +# Javascript Routing + +The play router is able to generate Javascript code to handle routing from Javascript running client side back to your application. The Javascript router aids in refactoring your application. If you change the structure of your URLs or parameter names your Javascript gets automatically updated to use that new structure. + +## Generating a Javascript router + +The first step to using Play's Javascript router is to generate it. The router will only expose the routes that you explicitly declare thus minimizing the size of the Javascript code. + +There are two ways to generate a Javascript router. One is to embed the router in the HTML page using template directives. The other is to generate Javascript resources in an action that can be downloaded, cached and shared between pages. + +### Embedded router + +An embedded router can be generated using the ``@javascriptRouter`` directive inside a Scala template. This is typically done inside the main decorating template. + +@[javascript-embedded-router](code/javaEmbeddedRouter.scala.html) + +The first parameter is the name of the global variable that the router will be placed in. The second parameter is the list of Javascript routes that should be included in this router. + +### Router resource + +A router resource can be generated by creating an action that invokes the router generator. It has a similar syntax to embedding the router in a template: + +@[javascript-router-resource-imports](code/javaguide/binder/controllers/Application.java) + +@[javascript-router-resource](code/javaguide/binder/controllers/Application.java) + +Then, add the corresponding route: + +@[javascript-router-routes](code/javaguide.binder.routes) + +Having implemented this action, and adding it to your routes file, you can then include it as a resource in your templates: + +```html + +``` + +## Using the router + +Using jQuery as an example, making a call is as simple as: + +```javascript +$.ajax(jsRoutes.controllers.Users.get(someId)) + .done( /*...*/ ) + .fail( /*...*/ ); +``` + +The router also makes a few other properties available including the ``url`` and the ``type`` (the HTTP method). For example the above call to jQuery's ajax function can also be made like: + +```javascript +var r = jsRoutes.controllers.Users.get(someId); +$.ajax({url: r.url, type: r.type, success: /*...*/, error: /*...*/ }); +``` + +The above approach is required where other properties need setting such as success, error, context etc. + +The ``absoluteURL`` and the ``webSocketURL`` are methods (not properties) which return the complete url string. A Websocket connection can be made like: + +```javascript +var r = jsRoutes.controllers.Users.list(); +var ws = new WebSocket(r.webSocketURL()); +ws.onmessage = function(msg) { + /*...*/ +}; +``` + +## jQuery ajax method support + +> **Note:** Built-in support for jQuery's ajax function will be removed in a future release. This section on the built-in support is provided for reference purposes only. Please do not use the router's ajax function in new code and consider upgrading existing code as soon as possible. The previous section on using the router documents how jQuery should be used. + +If jQuery isn't your thing, or if you'd like to decorate the jQuery ajax method in some way, you can provide a function to the router to use to perform ajax queries. This function must accept the object that is passed to the ``ajax`` router method, and should expect the router to have set the ``type`` and ``url`` properties on it to the appropriate method and url for the router request. + +To define this function, in your action pass the ``ajaxMethod`` method parameter, eg: + +@[javascript-router-resource-custom-method](code/javaguide/binder/controllers/Application.java) diff --git a/manual/working/javaGuide/advanced/routing/JavaRoutingDsl.md b/manual/working/javaGuide/advanced/routing/JavaRoutingDsl.md index 9f8308f9..0887eb3c 100644 --- a/manual/working/javaGuide/advanced/routing/JavaRoutingDsl.md +++ b/manual/working/javaGuide/advanced/routing/JavaRoutingDsl.md @@ -1,19 +1,27 @@ - + # Routing DSL Play provides a DSL for routers directly in code. This DSL has many uses, including embedding a light weight Play server, providing custom or more advanced routing capabilities to a regular Play application, and mocking REST services for testing. The DSL uses a path pattern syntax similar to Play's compiled routes files, extracting parameters out, and invoking actions implemented using lambdas. -The DSL is provided by [`RoutingDsl`](api/java/play/routing/RoutingDsl.html). Since you will be implementing actions, you may want to import tha static methods from [`Controller`](api/java/play/mvc/Controller.html), which includes factory methods for creating results, accessing the request, response and session. So typically you will want at least the following imports: +The DSL is provided by [`RoutingDsl`](api/java/play/routing/RoutingDsl.html). Since you will be implementing actions, you may want to import the static methods from [`Controller`](api/java/play/mvc/Controller.html), which includes factory methods for creating results, accessing the request, response and session. So typically you will want at least the following imports: @[imports](code/javaguide/advanced/routing/JavaRoutingDsl.java) +And then you may use [[Dependency Injection|JavaDependencyInjection]] to get a `RoutingDsl` instance: + +@[inject](code/javaguide/advanced/routing/JavaRoutingDsl.java) + +Or you can directly create a new instance: + +@[new-routing-dsl](code/javaguide/advanced/routing/JavaRoutingDsl.java) + A simple example of the DSL's use is: @[simple](code/javaguide/advanced/routing/JavaRoutingDsl.java) -The `:to` parameter is extracted out and passed as the first parameter to the router. Note that the name you give to parameters in the the path pattern is irrelevant, the important thing is that parameters in the path are in the same order as parameters in your lambda. You can have anywhere from 0 to 3 parameters in the path pattern, and other HTTP methods, such as `POST`, `PUT` and `DELETE` are supported. +The `:to` parameter is extracted out and passed as the first parameter to the router. Note that the name you give to parameters in the path pattern is irrelevant, the important thing is that parameters in the path are in the same order as parameters in your lambda. You can have anywhere from 0 to 3 parameters in the path pattern, and other HTTP methods, such as `POST`, `PUT` and `DELETE` are supported. Like Play's compiled router, the DSL also supports matching multi path segment parameters, this is done by prefixing the parameter with `*`: @@ -31,4 +39,32 @@ Supported types include `Integer`, `Long`, `Float`, `Double`, `Boolean`, and any Asynchronous actions are of course also supported, using the `routeAsync` method: -@[async](code/javaguide/advanced/routing/JavaRoutingDsl.java) \ No newline at end of file +@[async](code/javaguide/advanced/routing/JavaRoutingDsl.java) + +## Binding Routing DSL + +Configuring an application to use a Routing DSL can be achieved in many ways, depending on use case: + +### Embedding play + +An example of embedding a play server with Routing DSL can be found in [[Embedding Play|JavaEmbeddingPlay]] section. + +### Providing a DI router + +A router can be provided to the application similarly as detailed in [[Application Entry point|ScalaCompileTimeDependencyInjection#Application-entry-point]] and [[Providing a router|ScalaCompileTimeDependencyInjection#Providing-a-router]], using e.g. a java builder class and an application loader: + +@[load](code/AppLoader.java) + +### Providing a DI router with Guice + +A router via Guice could be provided with the following snippet: + +@[load-guice2](code/GuiceRouterProvider.java) + +and in the application loader: + +@[load-guice1](code/GuiceAppLoader.java) + +### Overriding binding + +A router can also be provided using e.g. GuiceApplicationBuilder in the application loader to override with custom router binding or module as detailed in [[Bindings and Modules|JavaTestingWithGuice#Override-bindings]] diff --git a/manual/working/javaGuide/advanced/routing/RequestBinders.md b/manual/working/javaGuide/advanced/routing/RequestBinders.md new file mode 100644 index 00000000..60ba2055 --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/RequestBinders.md @@ -0,0 +1,53 @@ + +# Custom Routing + +Play provides a mechanism to bind types from path or query string parameters. + +## PathBindable + +[PathBindable](api/java/play/mvc/PathBindable.html) allows to bind business objects from the URL path; this means we’ll be able to define routes like `/user/3` to call an action such as the following: + +### `controller` + +@[path](code/javaguide/binder/controllers/BinderApplication.java) + +The `user` parameter will automatically be retrieved using the id extracted from the URL path, e.g. with the following route definition: + +### `/conf/routes` + +@[user](code/javaguide.binder.routes) + +Any type `T` that implements [`PathBindable`](api/java/play/mvc/PathBindable.html) can be bound to/from a path parameter. +It defines abstract methods `bind` (build a value from the path) and `unbind` (build a path fragment from a value). + +For a class like: + +@[declaration](code/javaguide/binder/models/User.java) + +A simple example of the binder's use binding the `:id` path parameter: + +@[bind](code/javaguide/binder/models/User.java) + +In this example `findById` method is invoked to retrieve `User` instance. + +> **Note:** in a real application such method should be lightweight and not involve e.g. DB access, because the code is called on the server IO thread and must be totally non-blocking. You would therefore for example use simple objects identifier as path bindable, and retrieve the real values using action composition. + +## QueryStringBindable + +A similar mechanism is used for query string parameters; a route like `/age` can be defined to call an action such as: + +### `controller` + +@[query](code/javaguide/binder/controllers/BinderApplication.java) + +The `age` parameter will automatically be retrieved using parameters extracted from the query string e.g. `/age?from=1&to=10` + +Any type `T` that implements [`QueryStringBindable`](api/java/play/mvc/QueryStringBindable.html) can be bound to/from query one or more query string parameters. Similar to [`PathBindable`](api/java/play/mvc/PathBindable.html), it defines abstract methods `bind` and `unbind`. + +For a class like: + +@[declaration](code/javaguide/binder/models/AgeRange.java) + +A simple example of the binder's use binding the `:from` and `:to` query string parameters: + +@[bind](code/javaguide/binder/models/AgeRange.java) diff --git a/manual/working/javaGuide/advanced/routing/code/AppLoader.java b/manual/working/javaGuide/advanced/routing/code/AppLoader.java new file mode 100644 index 00000000..a417dc5b --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/code/AppLoader.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +import play.Application; +import play.ApplicationLoader; +import play.routing.Router; +import play.routing.RoutingDslComponentsFromContext; + +import static play.mvc.Results.ok; + +// #load +public class AppLoader implements ApplicationLoader { + public Application load(ApplicationLoader.Context context) { + return new MyComponents(context).application(); + } +} + +class MyComponents extends RoutingDslComponentsFromContext + implements play.filters.components.NoHttpFiltersComponents { + + MyComponents(ApplicationLoader.Context context) { + super(context); + } + + @Override + public Router router() { + return routingDsl().GET("/hello/:to").routeTo(to -> ok("Hello " + to)).build(); + } +} +// #load diff --git a/manual/working/javaGuide/advanced/routing/code/GuiceAppLoader.java b/manual/working/javaGuide/advanced/routing/code/GuiceAppLoader.java new file mode 100644 index 00000000..58e6338c --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/code/GuiceAppLoader.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +import play.ApplicationLoader; +import play.api.inject.BindingKey; +import play.api.inject.guice.GuiceableModule; +import play.api.inject.guice.GuiceableModule$; +import play.inject.guice.GuiceApplicationLoader; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// #load-guice1 +public class GuiceAppLoader extends GuiceApplicationLoader { + + @Override + protected GuiceableModule[] overrides(ApplicationLoader.Context context) { + GuiceableModule[] modules = super.overrides(context); + GuiceableModule module = + GuiceableModule$.MODULE$.fromPlayBinding( + new BindingKey<>(play.api.routing.Router.class) + .toProvider(GuiceRouterProvider.class) + .eagerly()); + + List copyModules = new ArrayList<>(Arrays.asList(modules)); + copyModules.add(module); + + return copyModules.toArray(new GuiceableModule[copyModules.size()]); + } +} +// #load-guice1 diff --git a/manual/working/javaGuide/advanced/routing/code/GuiceRouterProvider.java b/manual/working/javaGuide/advanced/routing/code/GuiceRouterProvider.java new file mode 100644 index 00000000..559e9a34 --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/code/GuiceRouterProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +import play.routing.RoutingDsl; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; + +import static play.mvc.Results.ok; + +// #load-guice2 +@Singleton +public class GuiceRouterProvider implements Provider { + + private final RoutingDsl routingDsl; + + @Inject + public GuiceRouterProvider(RoutingDsl routingDsl) { + this.routingDsl = routingDsl; + } + + @Override + public play.api.routing.Router get() { + return routingDsl.GET("/hello/:to").routeTo(to -> ok("Hello " + to)).build().asScala(); + } +} +// #load-guice2 diff --git a/manual/working/javaGuide/advanced/routing/code/javaEmbeddedRouter.scala.html b/manual/working/javaGuide/advanced/routing/code/javaEmbeddedRouter.scala.html new file mode 100644 index 00000000..c6f972a6 --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/code/javaEmbeddedRouter.scala.html @@ -0,0 +1,11 @@ +@* #javascript-embedded-router *@ +@() + +@* ###skip: 2 *@ +@import javaguide.binder.controllers.routes + +@helper.javascriptRouter("jsRoutes")( + routes.javascript.Users.list, + routes.javascript.Users.get +) +@* #javascript-embedded-router *@ diff --git a/manual/working/javaGuide/advanced/routing/code/javaguide.binder.routes b/manual/working/javaGuide/advanced/routing/code/javaguide.binder.routes new file mode 100644 index 00000000..07ed0612 --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/code/javaguide.binder.routes @@ -0,0 +1,14 @@ +# #user +GET /user/:user controllers.BinderApplication.user(user: javaguide.binder.models.User) +# #user + +# #ageRange +GET /ageRange controllers.BinderApplication.age(age: javaguide.binder.models.AgeRange) +# #ageRange + +GET /users/list controllers.Users.list +GET /users/:id controllers.Users.get(id: Long) + +# #javascript-router-routes +GET /javascriptRoutes controllers.Application.javascriptRoutes +# #javascript-router-routes diff --git a/manual/working/javaGuide/advanced/routing/code/javaguide/advanced/routing/JavaRoutingDsl.java b/manual/working/javaGuide/advanced/routing/code/javaguide/advanced/routing/JavaRoutingDsl.java index 0ecc1160..5dcaa70d 100644 --- a/manual/working/javaGuide/advanced/routing/code/javaguide/advanced/routing/JavaRoutingDsl.java +++ b/manual/working/javaGuide/advanced/routing/code/javaguide/advanced/routing/JavaRoutingDsl.java @@ -1,95 +1,127 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.advanced.routing; +import org.junit.Before; import org.junit.Test; -//#imports -import play.api.routing.Router; +// #imports +import javax.inject.Inject; + +import play.api.mvc.AnyContent; +import play.api.mvc.BodyParser; +import play.api.mvc.PlayBodyParsers; +import play.core.j.JavaContextComponents; +import play.routing.Router; import play.routing.RoutingDsl; -import play.libs.F; +import java.util.concurrent.CompletableFuture; + import static play.mvc.Controller.*; -//#imports +// #imports import play.mvc.Result; +import play.test.WithApplication; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static play.test.Helpers.*; -public class JavaRoutingDsl { - @Test - public void simple() { - //#simple - Router router = new RoutingDsl() - .GET("/hello/:to").routeTo(to -> - ok("Hello " + to) - ) - .build(); - //#simple +public class JavaRoutingDsl extends WithApplication { - assertThat(makeRequest(router, "GET", "/hello/world"), equalTo("Hello world")); - } + private RoutingDsl routingDsl; - @Test - public void fullPath() { - //#full-path - Router router = new RoutingDsl() - .GET("/assets/*file").routeTo(file -> - ok("Serving " + file) - ) - .build(); - //#full-path + @Before + public void initializeRoutingDsl() { + this.routingDsl = app.injector().instanceOf(RoutingDsl.class); + } - assertThat(makeRequest(router, "GET", "/assets/javascripts/main.js"), equalTo("Serving javascripts/main.js")); - } + @Test + public void simple() { + // #simple + Router router = routingDsl.GET("/hello/:to").routeTo(to -> ok("Hello " + to)).build(); + // #simple - @Test - public void regexp() { - //#regexp - Router router = new RoutingDsl() - .GET("/api/items/$id<[0-9]+>").routeTo(id -> - ok("Getting item " + id) - ) - .build(); - //#regexp + assertThat(makeRequest(router, "GET", "/hello/world"), equalTo("Hello world")); + } - assertThat(makeRequest(router, "GET", "/api/items/23"), equalTo("Getting item 23")); - } + @Test + public void fullPath() { + // #full-path + Router router = routingDsl.GET("/assets/*file").routeTo(file -> ok("Serving " + file)).build(); + // #full-path - @Test - public void integer() { - //#integer - Router router = new RoutingDsl() - .GET("/api/items/:id").routeTo((Integer id) -> - ok("Getting item " + id) - ) - .build(); - //#integer + assertThat( + makeRequest(router, "GET", "/assets/javascripts/main.js"), + equalTo("Serving javascripts/main.js")); + } - assertThat(makeRequest(router, "GET", "/api/items/23"), equalTo("Getting item 23")); - } + @Test + public void regexp() { + // #regexp + Router router = + routingDsl.GET("/api/items/$id<[0-9]+>").routeTo(id -> ok("Getting item " + id)).build(); + // #regexp - @Test - public void async() { - //#async - Router router = new RoutingDsl() - .GET("/api/items/:id").routeAsync((Integer id) -> - F.Promise.pure(ok("Getting item " + id)) - ) + assertThat(makeRequest(router, "GET", "/api/items/23"), equalTo("Getting item 23")); + } + + @Test + public void integer() { + // #integer + Router router = + routingDsl.GET("/api/items/:id").routeTo((Integer id) -> ok("Getting item " + id)).build(); + // #integer + + assertThat(makeRequest(router, "GET", "/api/items/23"), equalTo("Getting item 23")); + } + + @Test + public void async() { + // #async + Router router = + routingDsl + .GET("/api/items/:id") + .routeAsync((Integer id) -> CompletableFuture.completedFuture(ok("Getting item " + id))) .build(); - //#async + // #async - assertThat(makeRequest(router, "GET", "/api/items/23"), equalTo("Getting item 23")); + assertThat(makeRequest(router, "GET", "/api/items/23"), equalTo("Getting item 23")); + } + + private String makeRequest(Router router, String method, String path) { + Result result = routeAndCall(app, router, fakeRequest(method, path)); + if (result == null) { + return null; + } else { + return contentAsString(result); } + } + + // #inject + public class MyComponent { - private String makeRequest(Router router, String method, String path) { - Result result = routeAndCall(router, fakeRequest(method, path)); - if (result == null) { - return null; - } else { - return contentAsString(result); - } + private final RoutingDsl routingDsl; + + @Inject + public MyComponent(RoutingDsl routing) { + this.routingDsl = routing; } + } + // #inject + + @Test + public void createNewRoutingDsl() { + play.mvc.BodyParser.Default bodyParser = + app.injector().instanceOf(play.mvc.BodyParser.Default.class); + JavaContextComponents javaContextComponents = + app.injector().instanceOf(JavaContextComponents.class); + + // #new-routing-dsl + RoutingDsl routingDsl = new RoutingDsl(bodyParser, javaContextComponents); + // #new-routing-dsl + Router router = routingDsl.GET("/hello/:to").routeTo(to -> ok("Hello " + to)).build(); + + assertThat(makeRequest(router, "GET", "/hello/world"), equalTo("Hello world")); + } } diff --git a/manual/working/javaGuide/advanced/routing/code/javaguide/binder/controllers/Application.java b/manual/working/javaGuide/advanced/routing/code/javaguide/binder/controllers/Application.java new file mode 100644 index 00000000..4efa426f --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/code/javaguide/binder/controllers/Application.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.binder.controllers; + +// #javascript-router-resource-imports +import play.routing.JavaScriptReverseRouter; +import play.mvc.Controller; +import play.mvc.Result; +// #javascript-router-resource-imports + +public class Application extends Controller { + + // #javascript-router-resource + public Result javascriptRoutes() { + return ok(JavaScriptReverseRouter.create( + "jsRoutes", routes.javascript.Users.list(), routes.javascript.Users.get())) + .as("text/javascript"); + } + // #javascript-router-resource + + public Result javascriptRoutes2() { + return ok( + // #javascript-router-resource-custom-method + JavaScriptReverseRouter.create( + "jsRoutes", + "myAjaxMethod", + routes.javascript.Users.list(), + routes.javascript.Users.get()) + // #javascript-router-resource-custom-method + ) + .as("text/javascript"); + } +} diff --git a/manual/working/javaGuide/advanced/routing/code/javaguide/binder/controllers/BinderApplication.java b/manual/working/javaGuide/advanced/routing/code/javaguide/binder/controllers/BinderApplication.java new file mode 100644 index 00000000..e5ea847a --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/code/javaguide/binder/controllers/BinderApplication.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.binder.controllers; + +import javaguide.binder.models.User; +import javaguide.binder.models.AgeRange; +import play.mvc.Controller; +import play.mvc.Result; + +public class BinderApplication extends Controller { + + // #path + public Result user(User user) { + return ok(user.name); + } + // #path + + // #query + public Result age(AgeRange ageRange) { + return ok(String.valueOf(ageRange.from)); + } + // #query +} diff --git a/manual/working/javaGuide/advanced/routing/code/javaguide/binder/controllers/Users.java b/manual/working/javaGuide/advanced/routing/code/javaguide/binder/controllers/Users.java new file mode 100644 index 00000000..7ca14b84 --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/code/javaguide/binder/controllers/Users.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.binder.controllers; + +import play.mvc.Controller; +import play.mvc.Result; + +public class Users extends Controller { + + public Result list() { + return ok("List Users"); + } + + public Result get(Long id) { + return ok("Get user by id"); + } +} diff --git a/manual/working/javaGuide/advanced/routing/code/javaguide/binder/models/AgeRange.java b/manual/working/javaGuide/advanced/routing/code/javaguide/binder/models/AgeRange.java new file mode 100644 index 00000000..692e66a3 --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/code/javaguide/binder/models/AgeRange.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.binder.models; + +import java.util.Map; +import java.util.Optional; +import play.libs.F; +import play.libs.F.*; +import play.mvc.QueryStringBindable; +// import java.util.Optional; + +// #declaration +public class AgeRange implements QueryStringBindable { + + public Integer from; + public Integer to; + // #declaration + + // #bind + @Override + public Optional bind(String key, Map data) { + + try { + from = new Integer(data.get("from")[0]); + to = new Integer(data.get("to")[0]); + return Optional.of(this); + + } catch (Exception e) { // no parameter match return None + return Optional.empty(); + } + } + + @Override + public String unbind(String key) { + return new StringBuilder().append("from=").append(from).append("&to=").append(to).toString(); + } + // #bind + + @Override + public String javascriptUnbind() { + return new StringBuilder().append("from=").append(from).append(";to=").append(to).toString(); + } +} diff --git a/manual/working/javaGuide/advanced/routing/code/javaguide/binder/models/User.java b/manual/working/javaGuide/advanced/routing/code/javaguide/binder/models/User.java new file mode 100644 index 00000000..970887c6 --- /dev/null +++ b/manual/working/javaGuide/advanced/routing/code/javaguide/binder/models/User.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.binder.models; + +import play.mvc.PathBindable; + +// #declaration +public class User implements PathBindable { + + public Long id; + public String name; + // #declaration + + // #bind + @Override + public User bind(String key, String id) { + + // findById meant to be lightweight operation + User user = findById(new Long(id)); + if (user == null) { + throw new IllegalArgumentException("User with id " + id + " not found"); + } + return user; + } + + @Override + public String unbind(String key) { + return String.valueOf(id); + } + // #bind + + @Override + public String javascriptUnbind() { + return "function(k,v) {\n" + " return v.id;" + "}"; + } + + // stubbed test + // designed to be lightweight operation + private User findById(Long id) { + if (id > 3) return null; + User user = new User(); + user.id = id; + user.name = "User " + String.valueOf(id); + return user; + } +} diff --git a/manual/working/javaGuide/advanced/routing/index.toc b/manual/working/javaGuide/advanced/routing/index.toc index 11c02f38..09835ca6 100644 --- a/manual/working/javaGuide/advanced/routing/index.toc +++ b/manual/working/javaGuide/advanced/routing/index.toc @@ -1 +1,3 @@ -JavaRoutingDsl:組み込みルーティング DSL \ No newline at end of file +JavaJavascriptRouter: Javascript Router +JavaRoutingDsl:Embedded Routing DSL +RequestBinders:Custom Binding diff --git a/manual/working/javaGuide/code/MockJavaAction.scala b/manual/working/javaGuide/code/MockJavaAction.scala index 1e6eec2d..f0accf41 100644 --- a/manual/working/javaGuide/code/MockJavaAction.scala +++ b/manual/working/javaGuide/code/MockJavaAction.scala @@ -1,80 +1,123 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ -package javaguide.testhelpers - -import play.api.mvc.{Action, Request} -import play.core.j.{JavaHandlerComponents, JavaHelpers, JavaActionAnnotations, JavaAction} -import play.http.DefaultHttpRequestHandler -import play.mvc.{Controller, Http, Result} -import play.api.test.Helpers -import play.libs.F -import java.lang.reflect.Method - -abstract class MockJavaAction extends Controller with Action[Http.RequestBody] { self => - - private lazy val action = new JavaAction(new JavaHandlerComponents( - play.api.Play.current.injector, new DefaultHttpRequestHandler - )) { - val annotations = new JavaActionAnnotations(controller, method) - def parser = annotations.parser - def invocation = self.invocation - } +package javaguide.testhelpers { + + import java.util.concurrent.CompletableFuture + import java.util.concurrent.CompletionStage + + import play.api.mvc.Action + import play.api.mvc.Request + import play.core.j._ + import play.mvc.Controller + import play.mvc.Http + import play.mvc.Result + import play.api.test.Helpers + import java.lang.reflect.Method + + import akka.stream.Materializer + + import scala.concurrent.ExecutionContext + + abstract class MockJavaAction(handlerComponents: JavaHandlerComponents) + extends Controller + with Action[Http.RequestBody] { + self => - def parser = action.parser - def apply(request: Request[Http.RequestBody]) = action.apply(request) + private lazy val action = new JavaAction(handlerComponents) { + val annotations = + new JavaActionAnnotations(controller, method, handlerComponents.httpConfiguration.actionComposition) - private val controller = this.getClass - private val method = MockJavaActionJavaMocker.findActionMethod(this) - def invocation = { - method.invoke(this) match { - case r: Result => F.Promise.pure(r) - case f: F.Promise[_] => f.asInstanceOf[F.Promise[Result]] + def parser = { + play.HandlerInvokerFactoryAccessor.javaBodyParserToScala( + handlerComponents.getBodyParser(annotations.parser) + ) + } + + def invocation = self.invocation } - } -} -object MockJavaActionHelper { - import Helpers.defaultAwaitTimeout + def parser = action.parser + + def apply(request: Request[Http.RequestBody]) = action.apply(request) + + private val controller = this.getClass + private val method = MockJavaActionJavaMocker.findActionMethod(this) - def call(action: Action[Http.RequestBody], requestBuilder: play.mvc.Http.RequestBuilder): Result = { - val result = Helpers.await(action.apply(requestBuilder.build()._underlyingRequest)) - new Result { - def toScala = result + def executionContext: ExecutionContext = handlerComponents.executionContext + + def invocation = { + method.invoke(this) match { + case r: Result => CompletableFuture.completedFuture(r) + case f: CompletionStage[_] => f.asInstanceOf[CompletionStage[Result]] + } } } - def callWithStringBody(action: Action[Http.RequestBody], requestBuilder: play.mvc.Http.RequestBuilder, body: String): Result = { - val result = Helpers.await(Helpers.call(action, requestBuilder.build()._underlyingRequest, body)) - new Result { - def toScala = result + object MockJavaActionHelper { + + import Helpers.defaultAwaitTimeout + + def call(action: Action[Http.RequestBody], requestBuilder: play.mvc.Http.RequestBuilder)( + implicit mat: Materializer + ): Result = { + Helpers + .await(requestBuilder.body() match { + case null => + action.apply(requestBuilder.build().asScala) + case other => + Helpers.call(action, requestBuilder.build().asScala, other.asBytes()) + }) + .asJava } + + def callWithStringBody( + action: Action[Http.RequestBody], + requestBuilder: play.mvc.Http.RequestBuilder, + body: String + )(implicit mat: Materializer): Result = { + Helpers.await(Helpers.call(action, requestBuilder.build().asScala, body)).asJava + } + + def setContext(request: play.mvc.Http.RequestBuilder, contextComponents: JavaContextComponents): Unit = { + Http.Context.current.set(JavaHelpers.createJavaContext(request.build().asScala, contextComponents)) + } + + def removeContext: Unit = Http.Context.current.remove() } - def setContext(request: play.mvc.Http.RequestBuilder): Unit = { - Http.Context.current.set(JavaHelpers.createJavaContext(request.build()._underlyingRequest)) + /** + * Java should be mocked. + * + * This object exists because if you put its implementation in the MockJavaAction, then when other things go + * + * import static MockJavaAction.*; + * + * They get a compile error from javac, and it seems to be because javac is trying ot import a synthetic method + * that it shouldn't. Hence, this object mocks java. + */ + object MockJavaActionJavaMocker { + def findActionMethod(obj: AnyRef): Method = { + val maybeMethod = obj.getClass.getDeclaredMethods.find { method => + !method.isSynthetic && method.getParameterCount == 0 + } + val theMethod = maybeMethod.getOrElse( + throw new RuntimeException("MockJavaAction must declare at least one non synthetic method") + ) + theMethod.setAccessible(true) + theMethod + } } - def removeContext: Unit = Http.Context.current.remove() } /** - * Java should be mocked. - * - * This object exists because if you put its implementation in the MockJavaAction, then when other things go - * - * import static MockJavaAction.*; - * - * They get a compile error from javac, and it seems to be because javac is trying ot import a synthetic method - * that it shouldn't. Hence, this object mocks java. + * javaBodyParserToScala is private to play */ -object MockJavaActionJavaMocker { - def findActionMethod(obj: AnyRef): Method = { - val maybeMethod = obj.getClass.getDeclaredMethods.find(!_.isSynthetic) - val theMethod = maybeMethod.getOrElse( - throw new RuntimeException("MockJavaAction must declare at least one non synthetic method") - ) - theMethod.setAccessible(true) - theMethod +package play { + + object HandlerInvokerFactoryAccessor { + val javaBodyParserToScala = play.core.routing.HandlerInvokerFactory.javaBodyParserToScala _ } + } diff --git a/manual/working/javaGuide/index.toc b/manual/working/javaGuide/index.toc index 8e811832..4b36fb15 100644 --- a/manual/working/javaGuide/index.toc +++ b/manual/working/javaGuide/index.toc @@ -1,3 +1,2 @@ -JavaHome:目次 -main:主要なコンセプト -advanced:上級編 \ No newline at end of file +!main:Main concepts +!advanced:Advanced topics \ No newline at end of file diff --git a/manual/working/javaGuide/main/JavaHome.md b/manual/working/javaGuide/main/JavaHome.md new file mode 100644 index 00000000..ff560fe7 --- /dev/null +++ b/manual/working/javaGuide/main/JavaHome.md @@ -0,0 +1,8 @@ + +# Main concepts for Java + +This section introduces you to the most common aspects of writing a Play application in Java. You'll learn about handling HTTP requests, sending HTTP responses, working with different types of data, using databases and much more. + +> **Note:** The Play APIs for Java and Scala are separated into different packages. All the Java APIs are under the `play` package; all the Scala APIs are under `play.api`. For example, the Java MVC API is under `play.mvc` and the Scala MVC API is under `play.api.mvc`. + +@toc@ diff --git a/manual/working/javaGuide/main/akka/JavaAkka.md b/manual/working/javaGuide/main/akka/JavaAkka.md index a1bd78d7..d056189e 100644 --- a/manual/working/javaGuide/main/akka/JavaAkka.md +++ b/manual/working/javaGuide/main/akka/JavaAkka.md @@ -1,7 +1,7 @@ - + # Integrating with Akka -[Akka](http://akka.io/) uses the Actor Model to raise the abstraction level and provide a better platform to build correct concurrent and scalable applications. For fault-tolerance it adopts the ‘Let it crash’ model, which has been used with great success in the telecoms industry to build applications that self-heal - systems that never stop. Actors also provide the abstraction for transparent distribution and the basis for truly scalable and fault-tolerant applications. +[Akka](https://akka.io/) uses the Actor Model to raise the abstraction level and provide a better platform to build correct concurrent and scalable applications. For fault-tolerance it adopts the ‘Let it crash’ model, which has been used with great success in the telecoms industry to build applications that self-heal - systems that never stop. Actors also provide the abstraction for transparent distribution and the basis for truly scalable and fault-tolerant applications. ## The application actor system @@ -15,7 +15,7 @@ To start using Akka, you need to write an actor. Below is a simple actor that s @[actor](code/javaguide/akka/HelloActor.java) -Notice here that the `HelloActor` defines a static method called `props`, this returns a `Props` object that describes how to create the actor. This is a good Akka convention, to separate the instantiation logic from the code that creates the actor. +Notice here that the `HelloActor` defines a static method called `getProps`, this method returns a `Props` object that describes how to create the actor. This is a good Akka convention, to separate the instantiation logic from the code that creates the actor. Another best practice shown here is that the messages that `HelloActor` sends and receives are defined as static inner classes of another class called `HelloActorProtocol`: @@ -27,7 +27,7 @@ To create and/or use an actor, you need an `ActorSystem`. This can be obtained The most basic thing that you can do with an actor is send it a message. When you send a message to an actor, there is no response, it's fire and forget. This is also known as the _tell_ pattern. -In a web application however, the _tell_ pattern is often not useful, since HTTP is a protocol that has requests and responses. In this case, it is much more likely that you will want to use the _ask_ pattern. The ask pattern returns a Scala `Future`, which you can then wrap in a Play `Promise`, and then map to your own result type. +In a web application however, the _tell_ pattern is often not useful, since HTTP is a protocol that has requests and responses. In this case, it is much more likely that you will want to use the _ask_ pattern. The ask pattern returns a Scala `Future`, which you can convert to a Java `CompletionStage` using `scala.compat.java8.FutureConverts.toJava`, and then map to your own result type. Below is an example of using our `HelloActor` with the ask pattern: @@ -36,7 +36,7 @@ Below is an example of using our `HelloActor` with the ask pattern: A few things to notice: * The ask pattern needs to be imported, it's often most convenient to static import the `ask` method. -* The returned future is wrapped in a `Promise`. The resulting promise is a `Promise`, so when you access its value, you need to cast it to the type you are expecting back from the actor. +* The returned future is converted to a `CompletionStage`. The resulting promise is a `CompletionStage`, so when you access its value, you need to cast it to the type you are expecting back from the actor. * The ask pattern requires a timeout, we have supplied 1000 milliseconds. If the actor takes longer than that to respond, the returned promise will be completed with a timeout error. * Since we're creating the actor in the constructor, we need to scope our controller as `Singleton`, so that a new actor isn't created every time this controller is used. @@ -78,7 +78,7 @@ Now, the actor that depends on this can extend [`InjectedActorSupport`](api/java @[injectedparent](code/javaguide/akka/ParentActor.java) -It uses the `injectedChild` to create and get a reference to the child actor, passing in the key. +It uses the `injectedChild` to create and get a reference to the child actor, passing in the key. The second parameter (`key` in this example) will be used as the child actor's name. Finally, we need to bind our actors. In our module, we use the `bindActorFactory` method to bind the parent actor, and also bind the child factory to the child implementation: @@ -105,7 +105,7 @@ play.akka.config = "my-akka" Now settings will be read from the `my-akka` prefix instead of the `akka` prefix. ``` -my-akka.actor.default-dispatcher.fork-join-executor.pool-size-max = 64 +my-akka.actor.default-dispatcher.fork-join-executor.parallelism-max = 64 my-akka.actor.debug.receive = on ``` @@ -117,7 +117,7 @@ By default the name of the Play actor system is `application`. You can change th play.akka.actor-system = "custom-name" ``` -> **Note:** This feature is useful if you want to put your play application ActorSystem in an akka cluster. +> **Note:** This feature is useful if you want to put your play application `ActorSystem` in an akka cluster. ## Executing a block of code asynchronously @@ -125,14 +125,39 @@ A common use case within Akka is to have some computation performed concurrently @[async](code/javaguide/akka/async/Application.java) -## Scheduling asynchronous tasks +## Akka Coordinated Shutdown -You can schedule sending messages to actors and executing tasks (functions or `Runnable` instances). You will get a `Cancellable` back that you can call `cancel` on to cancel the execution of the scheduled operation. +Play terminates the built-in Akka actor system using Akka's [Coordinated Shutdown](https://doc.akka.io/docs/akka/current/actors.html?language=java#coordinated-shutdown). By default Play will run the Coordinated Shutdown using only the last phase where the actor system is terminated. You can override that settings using: -For example, to send a message to the `testActor` every 30 minutes: +``` +# Runs Akka CoordinatedShutdown for Play's actor system +# from phase "service-stop". See Akka docs on Coordinated +# Shutdown for other phase names. In Play, this defaults +# to "actor-system-terminate" +play.akka.run-cs-from-phase = "service-stop" +``` + +If you are using extra actor systems in your Play Application and tests, make sure they are all terminated and feel free to migrate your termination code from the traditional `actorSystem.terminate()` to the new [Coordinated Shutdown](https://doc.akka.io/docs/akka/current/actors.html?language=java#coordinated-shutdown) + +## Akka Cluster + +You can make your Play application join an existing [Akka Cluster](https://doc.akka.io/docs/akka/snapshot/cluster-usage.html). In that case it is recommended that you leave the cluster gracefully. Play ships with Akka's Coordinated Shutdown since Play 2.6 which can take care of that graceful leave but it is disabled. + +If you are joining an Akka Cluster with your Play application and you already have custom Cluster Leave code it is recommended that you replace it and enable Akka's handling using `play.akka.run-cs-from-phase` described above and set it to run from, at least, `before-cluster-shutdown` phase. See [Akka docs](https://doc.akka.io/docs/akka/current/actors.html?language=java#coordinated-shutdown) for more details. + +## Updating Akka version + +If you want to use a newer version of Akka, one that is not used by Play yet, you can add the following to your `build.sbt` file: + +@[akka-update](code/javaguide.akkaupdate.sbt) + +Of course, other Akka artifacts can be added transitively. Use [sbt-dependency-graph](https://github.com/jrudolph/sbt-dependency-graph) to better inspect your build and check which ones you need to add explicitly. + +If you also want to update Akka HTTP, you should also add its dependencies explicitly: + +@[akka-http-update](code/javaguide.akkaupdate.sbt) -@[schedule-actor](code/javaguide/akka/JavaAkka.java) +> **Note:** When doing such updates, keep in mind that you need to follow Akka's [Binary Compatibility Rules](https://doc.akka.io/docs/akka/2.5/common/binary-compatibility-rules.html). And if you are manually adding other Akka artifacts, remember to keep the version of all the Akka artifacts consistent since [mixed versioning is not allowed](https://doc.akka.io/docs/akka/2.5/common/binary-compatibility-rules.html#mixed-versioning-is-not-allowed). -Alternatively, to run a block of code ten milliseconds from now: +> **Note:** When resolving dependencies, sbt will get the newest one declared for this project or added transitively. It means that if Play depends on a newer Akka (or Akka HTTP) version than the one you are declaring, Play version wins. See more details about [how sbt does evictions here](https://www.scala-sbt.org/1.x/docs/Library-Management.html#Eviction+warning). -@[schedule-code](code/javaguide/akka/JavaAkka.java) diff --git a/manual/working/javaGuide/main/akka/code/javaguide.akkaupdate.sbt b/manual/working/javaGuide/main/akka/code/javaguide.akkaupdate.sbt new file mode 100644 index 00000000..5571dc93 --- /dev/null +++ b/manual/working/javaGuide/main/akka/code/javaguide.akkaupdate.sbt @@ -0,0 +1,29 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + +//#akka-update +// The newer Akka version you want to use. +val akkaVersion = "2.5.16" + +// Akka dependencies used by Play +libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-actor" % akkaVersion, + "com.typesafe.akka" %% "akka-stream" % akkaVersion, + "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, + // Only if you are using Akka Testkit + "com.typesafe.akka" %% "akka-testkit" % akkaVersion +) +//#akka-update + +//#akka-http-update +// The newer Akka HTTP version you want to use. +val akkaHTTPVersion = "10.1.4" + +// Akka HTTP dependencies used by Play +libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-http-core" % akkaHTTPVersion, + // Add this one if you are using HTTP/2 + "com.typesafe.akka" %% "akka-http2-support" % akkaHTTPVersion +) +//#akka-http-update diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredActor.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredActor.java index 6ecaaf8c..f07b4397 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredActor.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredActor.java @@ -1,20 +1,32 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ package javaguide.akka; -//#injected -import akka.actor.UntypedActor; -import play.Configuration; +// #injected +import akka.actor.AbstractActor; +import com.typesafe.config.Config; import javax.inject.Inject; -public class ConfiguredActor extends UntypedActor { +public class ConfiguredActor extends AbstractActor { - @Inject Configuration configuration; + private Config configuration; - @Override - public void onReceive(Object message) throws Exception { - if (message instanceof ConfiguredActorProtocol.GetConfig) { - sender().tell(configuration.getString("my.config"), self()); - } - } + @Inject + public ConfiguredActor(Config configuration) { + this.configuration = configuration; + } + + @Override + public Receive createReceive() { + return receiveBuilder() + .match( + ConfiguredActorProtocol.GetConfig.class, + message -> { + sender().tell(configuration.getString("my.config"), self()); + }) + .build(); + } } -//#injected +// #injected diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredActorProtocol.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredActorProtocol.java index 3d196c48..2b0e1b1a 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredActorProtocol.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredActorProtocol.java @@ -1,9 +1,9 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka; public class ConfiguredActorProtocol { - public static class GetConfig {} + public static class GetConfig {} } diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredChildActor.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredChildActor.java index e5f71d5a..d279263a 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredChildActor.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredChildActor.java @@ -1,31 +1,35 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka; -//#injectedchild -import akka.actor.UntypedActor; +// #injectedchild +import akka.actor.AbstractActor; import com.google.inject.assistedinject.Assisted; -import play.Configuration; +import com.typesafe.config.Config; import javax.inject.Inject; -public class ConfiguredChildActor extends UntypedActor { +public class ConfiguredChildActor extends AbstractActor { - private final Configuration configuration; - private final String key; + private final Config configuration; + private final String key; - @Inject - public ConfiguredChildActor(Configuration configuration, @Assisted String key) { - this.configuration = configuration; - this.key = key; - } + @Inject + public ConfiguredChildActor(Config configuration, @Assisted String key) { + this.configuration = configuration; + this.key = key; + } - @Override - public void onReceive(Object message) throws Exception { - if (message instanceof ConfiguredChildActorProtocol.GetConfig) { - sender().tell(configuration.getString(key), self()); - } - } + @Override + public Receive createReceive() { + return receiveBuilder() + .match(ConfiguredChildActorProtocol.GetConfig.class, this::getConfig) + .build(); + } + + private void getConfig(ConfiguredChildActorProtocol.GetConfig get) { + sender().tell(configuration.getString(key), self()); + } } -//#injectedchild +// #injectedchild diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredChildActorProtocol.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredChildActorProtocol.java index 0de78481..ba42a784 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredChildActorProtocol.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/ConfiguredChildActorProtocol.java @@ -1,17 +1,17 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka; -//#protocol +// #protocol import akka.actor.Actor; public class ConfiguredChildActorProtocol { - public static class GetConfig {} + public static class GetConfig {} - public interface Factory { - public Actor create(String key); - } + public interface Factory { + public Actor create(String key); + } } -//#protocol +// #protocol diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/HelloActor.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/HelloActor.java index 95b01507..809e1b87 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/HelloActor.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/HelloActor.java @@ -1,22 +1,31 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ -//#actor -//###replace: package actors; +// #actor +// ###replace: package actors; package javaguide.akka; import akka.actor.*; -//###replace: import actors.HelloActorProtocol.*; +import akka.japi.*; +// ###replace: import actors.HelloActorProtocol.*; import javaguide.akka.HelloActorProtocol.*; -public class HelloActor extends UntypedActor { +public class HelloActor extends AbstractActor { - public static Props props = Props.create(HelloActor.class); + public static Props getProps() { + return Props.create(HelloActor.class); + } - public void onReceive(Object msg) throws Exception { - if (msg instanceof SayHello) { - sender().tell("Hello, " + ((SayHello) msg).name, self()); - } - } + @Override + public Receive createReceive() { + return receiveBuilder() + .match( + SayHello.class, + hello -> { + String reply = "Hello, " + hello.name; + sender().tell(reply, self()); + }) + .build(); + } } -//#actor +// #actor diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/HelloActorProtocol.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/HelloActorProtocol.java index 60979774..752dc8ef 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/HelloActorProtocol.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/HelloActorProtocol.java @@ -1,18 +1,18 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ -//#protocol -//###replace: package actors; +// #protocol +// ###replace: package actors; package javaguide.akka; public class HelloActorProtocol { - public static class SayHello { - public final String name; + public static class SayHello { + public final String name; - public SayHello(String name) { - this.name = name; - } + public SayHello(String name) { + this.name = name; } + } } -//#protocol +// #protocol diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/JavaAkka.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/JavaAkka.java index ba2d759a..6e802e39 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/JavaAkka.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/JavaAkka.java @@ -1,24 +1,24 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka; import akka.actor.*; -import static akka.pattern.Patterns.ask; +import static akka.pattern.PatternsCS.ask; import com.typesafe.config.*; import javaguide.testhelpers.MockJavaAction; import javaguide.testhelpers.MockJavaActionHelper; import org.junit.Test; import play.Application; +import play.core.j.JavaHandlerComponents; import play.inject.guice.GuiceApplicationBuilder; -import play.libs.F.Promise; import play.mvc.Result; -import play.test.*; +import scala.compat.java8.FutureConverters; +import scala.concurrent.*; import scala.concurrent.duration.Duration; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; @@ -26,127 +26,118 @@ public class JavaAkka { - private static volatile CountDownLatch latch; - public static class MyActor extends UntypedActor { - @Override - public void onReceive(Object msg) throws Exception { - latch.countDown(); - } + private static volatile CountDownLatch latch; + + public static class MyActor extends AbstractActor { + @Override + public Receive createReceive() { + return receiveBuilder() + .matchAny( + m -> { + latch.countDown(); + }) + .build(); } - - @Test - public void testask() throws Exception { - Application app = fakeApplication(); - running(app, () -> { - javaguide.akka.ask.Application controller = app.injector().instanceOf(javaguide.akka.ask.Application.class); - - String message = contentAsString(controller.sayHello("world").get(1000)); + } + + @Test + public void testask() throws Exception { + Application app = fakeApplication(); + running( + app, + () -> { + javaguide.akka.ask.Application controller = + app.injector().instanceOf(javaguide.akka.ask.Application.class); + + try { + String message = + contentAsString( + controller.sayHello("world").toCompletableFuture().get(1, TimeUnit.SECONDS)); assertThat(message, equalTo("Hello, world")); + } catch (Exception e) { + throw new RuntimeException(e); + } }); - } + } - @Test - public void injected() throws Exception { - Application app = new GuiceApplicationBuilder() + @Test + public void injected() throws Exception { + Application app = + new GuiceApplicationBuilder() .bindings(new javaguide.akka.modules.MyModule()) .configure("my.config", "foo") .build(); - running(app, () -> { - javaguide.akka.inject.Application controller = app.injector().instanceOf(javaguide.akka.inject.Application.class); - - String message = contentAsString(controller.getConfig().get(1000)); + running( + app, + () -> { + javaguide.akka.inject.Application controller = + app.injector().instanceOf(javaguide.akka.inject.Application.class); + + try { + String message = + contentAsString( + controller.getConfig().toCompletableFuture().get(1, TimeUnit.SECONDS)); assertThat(message, equalTo("foo")); + } catch (Exception e) { + throw new RuntimeException(e); + } }); - } + } - @Test - public void factoryinjected() throws Exception { - Application app = new GuiceApplicationBuilder() + @Test + public void factoryinjected() throws Exception { + Application app = + new GuiceApplicationBuilder() .bindings(new javaguide.akka.factorymodules.MyModule()) .configure("my.config", "foo") .build(); - running(app, () -> { - ActorRef parent = app.injector().instanceOf(play.inject.Bindings.bind(ActorRef.class).qualifiedWith("parent-actor")); - - String message = (String) Promise.wrap(ask(parent, new ParentActorProtocol.GetChild("my.config"), 1000)).flatMap(child -> - Promise.wrap(ask((ActorRef) child, new ConfiguredChildActorProtocol.GetConfig(), 1000)) - ).get(5000); - + running( + app, + () -> { + ActorRef parent = + app.injector() + .instanceOf( + play.inject.Bindings.bind(ActorRef.class).qualifiedWith("parent-actor")); + + try { + String message = + (String) + ask(parent, new ParentActorProtocol.GetChild("my.config"), 1000) + .thenApply(msg -> (ActorRef) msg) + .thenCompose( + child -> ask(child, new ConfiguredChildActorProtocol.GetConfig(), 1000)) + .toCompletableFuture() + .get(5, TimeUnit.SECONDS); assertThat(message, equalTo("foo")); + } catch (Exception e) { + throw new RuntimeException(e); + } }); - } - - @Test - public void conf() throws Exception { - Config config = ConfigFactory.parseURL(getClass().getResource("akka.conf")); - ActorSystem.create("conf", config).shutdown(); - } - - @Test - public void async() throws Exception { - Application app = fakeApplication(); - running(app, () -> { - Result result = MockJavaActionHelper.call(new MockJavaAction() { - public Promise index() { - return new javaguide.akka.async.Application().index(); - } - }, fakeRequest()); - assertThat(contentAsString(result), equalTo("Got 2")); + } + + @Test + public void conf() throws Exception { + Config config = ConfigFactory.parseURL(getClass().getResource("akka.conf")); + scala.concurrent.Future future = ActorSystem.create("conf", config).terminate(); + Await.ready(future, Duration.create("10s")); + } + + @Test + public void async() throws Exception { + Application app = fakeApplication(); + running( + app, + () -> { + Result result = + MockJavaActionHelper.call( + new MockJavaAction(app.injector().instanceOf(JavaHandlerComponents.class)) { + public CompletionStage index() { + return new javaguide.akka.async.Application().index(); + } + }, + fakeRequest(), + app.getWrappedApplication().materializer()); + assertThat(contentAsString(result), equalTo("Got 2")); }); - } - - @Test - public void scheduleActor() throws Exception { - Application app = fakeApplication(); - running(app, () -> { - ActorSystem system = app.injector().instanceOf(ActorSystem.class); - latch = new CountDownLatch(1); - ActorRef testActor = system.actorOf(Props.create(MyActor.class)); - //#schedule-actor - system.scheduler().schedule( - Duration.create(0, TimeUnit.MILLISECONDS), //Initial delay 0 milliseconds - Duration.create(30, TimeUnit.MINUTES), //Frequency 30 minutes - testActor, - "tick", - system.dispatcher(), - null - ); - //#schedule-actor - try { - assertTrue(latch.await(5, TimeUnit.SECONDS)); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - @Test - public void scheduleCode() throws Exception { - Application app = fakeApplication(); - running(app, () -> { - - ActorSystem system = app.getWrappedApplication().injector().instanceOf(ActorSystem.class); - final CountDownLatch latch = new CountDownLatch(1); - class MockFile { - void delete() { - latch.countDown(); - } - } - final MockFile file = new MockFile(); - //#schedule-code - system.scheduler().scheduleOnce( - Duration.create(10, TimeUnit.MILLISECONDS), - () -> file.delete(), - system.dispatcher() - ); - //#schedule-code - try { - assertTrue(latch.await(5, TimeUnit.SECONDS)); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - - } - + } } diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/ParentActor.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/ParentActor.java index d39cd95e..2d25c4ff 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/ParentActor.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/ParentActor.java @@ -1,26 +1,34 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka; -//#injectedparent +// #injectedparent + +import akka.actor.AbstractActor; import akka.actor.ActorRef; -import akka.actor.UntypedActor; import play.libs.akka.InjectedActorSupport; import javax.inject.Inject; -public class ParentActor extends UntypedActor implements InjectedActorSupport { +public class ParentActor extends AbstractActor implements InjectedActorSupport { + + private ConfiguredChildActorProtocol.Factory childFactory; + + @Inject + public ParentActor(ConfiguredChildActorProtocol.Factory childFactory) { + this.childFactory = childFactory; + } - @Inject ConfiguredChildActorProtocol.Factory childFactory; + @Override + public Receive createReceive() { + return receiveBuilder().match(ParentActorProtocol.GetChild.class, this::getChild).build(); + } - @Override - public void onReceive(Object message) throws Exception { - if (message instanceof ParentActorProtocol.GetChild) { - String key = ((ParentActorProtocol.GetChild) message).key; - ActorRef child = injectedChild(() -> childFactory.create(key), key); - sender().tell(child, self()); - } - } + private void getChild(ParentActorProtocol.GetChild msg) { + String key = msg.key; + ActorRef child = injectedChild(() -> childFactory.create(key), key); + sender().tell(child, self()); + } } -//#injectedparent +// #injectedparent diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/ParentActorProtocol.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/ParentActorProtocol.java index 3a833a5f..4e9317fb 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/ParentActorProtocol.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/ParentActorProtocol.java @@ -1,15 +1,15 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka; public class ParentActorProtocol { - public static class GetChild { - public final String key; + public static class GetChild { + public final String key; - public GetChild(String key) { - this.key = key; - } + public GetChild(String key) { + this.key = key; } + } } diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/akka.conf b/manual/working/javaGuide/main/akka/code/javaguide/akka/akka.conf index b8199844..7fa3b47f 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/akka.conf +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/akka.conf @@ -1,4 +1,4 @@ #conf -akka.actor.default-dispatcher.fork-join-executor.pool-size-max = 64 +akka.actor.default-dispatcher.fork-join-executor.parallelism-max = 64 akka.actor.debug.receive = on -#conf \ No newline at end of file +#conf diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/ask/Application.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/ask/Application.java index 97a2dc25..b84cc289 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/ask/Application.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/ask/Application.java @@ -1,31 +1,33 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka.ask; import javaguide.akka.HelloActor; import javaguide.akka.HelloActorProtocol.SayHello; -//#ask +// #ask import akka.actor.*; import play.mvc.*; -import play.libs.F.*; +import scala.compat.java8.FutureConverters; import javax.inject.*; +import java.util.concurrent.CompletionStage; import static akka.pattern.Patterns.ask; @Singleton public class Application extends Controller { - final ActorRef helloActor; + final ActorRef helloActor; - @Inject public Application(ActorSystem system) { - helloActor = system.actorOf(HelloActor.props); - } + @Inject + public Application(ActorSystem system) { + helloActor = system.actorOf(HelloActor.getProps()); + } - public Promise sayHello(String name) { - return Promise.wrap(ask(helloActor, new SayHello(name), 1000)) - .map(response -> ok((String) response)); - } + public CompletionStage sayHello(String name) { + return FutureConverters.toJava(ask(helloActor, new SayHello(name), 1000)) + .thenApply(response -> ok((String) response)); + } } -//#ask +// #ask diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/async/Application.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/async/Application.java index ae953089..37a95dd7 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/async/Application.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/async/Application.java @@ -1,20 +1,22 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka.async; -//#async -import play.libs.F.Promise; +// #async import play.mvc.*; -import static play.libs.F.Promise.promise; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; public class Application extends Controller { - public Promise index() { - return promise(() -> longComputation()) - .map((Integer i) -> ok("Got " + i)); - } - //###skip: 1 - public int longComputation() { return 2; } + public CompletionStage index() { + return CompletableFuture.supplyAsync(this::longComputation) + .thenApply((Integer i) -> ok("Got " + i)); + } + // ###skip: 1 + public int longComputation() { + return 2; + } } -//#async +// #async diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/factorymodules/MyModule.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/factorymodules/MyModule.java index 7f8ca0a9..ffc2b983 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/factorymodules/MyModule.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/factorymodules/MyModule.java @@ -1,20 +1,19 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka.factorymodules; import javaguide.akka.*; -//#factorybinding +// #factorybinding import com.google.inject.AbstractModule; import play.libs.akka.AkkaGuiceSupport; public class MyModule extends AbstractModule implements AkkaGuiceSupport { - @Override - protected void configure() { - bindActor(ParentActor.class, "parent-actor"); - bindActorFactory(ConfiguredChildActor.class, - ConfiguredChildActorProtocol.Factory.class); - } + @Override + protected void configure() { + bindActor(ParentActor.class, "parent-actor"); + bindActorFactory(ConfiguredChildActor.class, ConfiguredChildActorProtocol.Factory.class); + } } -//#factorybinding +// #factorybinding diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/inject/Application.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/inject/Application.java index 917994d4..c3c96f1c 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/inject/Application.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/inject/Application.java @@ -1,28 +1,34 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka.inject; + import javaguide.akka.ConfiguredActorProtocol; -//#inject +// #inject import akka.actor.ActorRef; -import play.libs.F; import play.mvc.*; +import scala.compat.java8.FutureConverters; import javax.inject.Inject; import javax.inject.Named; +import java.util.concurrent.CompletionStage; import static akka.pattern.Patterns.ask; public class Application extends Controller { - @Inject @Named("configured-actor") - ActorRef configuredActor; + private ActorRef configuredActor; + + @Inject + public Application(@Named("configured-actor") ActorRef configuredActor) { + this.configuredActor = configuredActor; + } - public F.Promise getConfig() { - return F.Promise.wrap(ask(configuredActor, - new ConfiguredActorProtocol.GetConfig(), 1000) - ).map(response -> ok((String) response)); - } + public CompletionStage getConfig() { + return FutureConverters.toJava( + ask(configuredActor, new ConfiguredActorProtocol.GetConfig(), 1000)) + .thenApply(response -> ok((String) response)); + } } -//#inject \ No newline at end of file +// #inject diff --git a/manual/working/javaGuide/main/akka/code/javaguide/akka/modules/MyModule.java b/manual/working/javaGuide/main/akka/code/javaguide/akka/modules/MyModule.java index 4d93f5a0..37c64fbb 100644 --- a/manual/working/javaGuide/main/akka/code/javaguide/akka/modules/MyModule.java +++ b/manual/working/javaGuide/main/akka/code/javaguide/akka/modules/MyModule.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.akka.modules; + import javaguide.akka.ConfiguredActor; -//#binding +// #binding import com.google.inject.AbstractModule; import play.libs.akka.AkkaGuiceSupport; public class MyModule extends AbstractModule implements AkkaGuiceSupport { - @Override - protected void configure() { - bindActor(ConfiguredActor.class, "configured-actor"); - } + @Override + protected void configure() { + bindActor(ConfiguredActor.class, "configured-actor"); + } } -//#binding +// #binding diff --git a/manual/working/javaGuide/main/akka/index.toc b/manual/working/javaGuide/main/akka/index.toc index 34eff86b..0d738937 100644 --- a/manual/working/javaGuide/main/akka/index.toc +++ b/manual/working/javaGuide/main/akka/index.toc @@ -1 +1 @@ -JavaAkka:Akka との統合 \ No newline at end of file +JavaAkka:Integrating with Akka \ No newline at end of file diff --git a/manual/working/javaGuide/main/application/JavaApplication.md b/manual/working/javaGuide/main/application/JavaApplication.md new file mode 100644 index 00000000..baf0c025 --- /dev/null +++ b/manual/working/javaGuide/main/application/JavaApplication.md @@ -0,0 +1,8 @@ + +# Application Settings + +A running instance of Play is built around the `Application` class, the starting point for most of the application state for Play. The Application is loaded through an `ApplicationLoader` and is configured with a disposable classloader so that changing a setting in development mode will reload the Application. Most of the `Application` settings are configurable, but more complex behavior can be hooked into Play by binding the various handlers to a specific instance through dependency injection. + +* [[Essential Actions|JavaEssentialAction]] +* [[HTTP filters|JavaHttpFilters]] +* [[Error handling|JavaErrorHandling]] diff --git a/manual/working/javaGuide/main/application/JavaErrorHandling.md b/manual/working/javaGuide/main/application/JavaErrorHandling.md new file mode 100644 index 00000000..33cb8159 --- /dev/null +++ b/manual/working/javaGuide/main/application/JavaErrorHandling.md @@ -0,0 +1,30 @@ + +# Handling errors + +There are two main types of errors that an HTTP application can return - client errors and server errors. Client errors indicate that the connecting client has done something wrong, server errors indicate that there is something wrong with the server. + +Play will in many circumstances automatically detect client errors - these include errors such as malformed header values, unsupported content types, and requests for resources that can't be found. Play will also in many circumstances automatically handle server errors - if your action code throws an exception, Play will catch this and generate a server error page to send to the client. + +The interface through which Play handles these errors is [`HttpErrorHandler`](api/java/play/http/HttpErrorHandler.html). It defines two methods, `onClientError`, and `onServerError`. + +## Supplying a custom error handler + +If you're using [`BuiltInComponents`](api/java/play/BuiltInComponents.html) to construct your app, override the `httpRequestHandler` method to return an instance of your custom handler. + +If you're using runtime dependency injection (e.g. Guice), the error handler can be dynamically loaded at runtime. The simplest way is to create a class in the root package called `ErrorHandler` that implements [`HttpErrorHandler`](api/java/play/http/HttpErrorHandler.html), for example: + +@[root](code/javaguide/application/root/ErrorHandler.java) + +If you don't want to place your error handler in the root package, or if you want to be able to configure different error handlers for different environments, you can do this by configuring the `play.http.errorHandler` configuration property in `application.conf`: + + play.http.errorHandler = "com.example.ErrorHandler" + +## Extending the default error handler + +Out of the box, Play's default error handler provides a lot of useful functionality. For example, in dev mode, when a server error occurs, Play will attempt to locate and render the piece of code in your application that caused that exception, so that you can quickly see and identify the problem. You may want to provide custom server errors in production, while still maintaining that functionality in development. To facilitate this, Play provides a [`DefaultHttpErrorHandler`](api/java/play/http/DefaultHttpErrorHandler.html) that has some convenience methods that you can override so that you can mix in your custom logic with Play's existing behavior. + +For example, to just provide a custom server error message in production, leaving the development error message untouched, and you also wanted to provide a specific forbidden error page: + +@[default](code/javaguide/application/def/ErrorHandler.java) + +Checkout the full API documentation for [`DefaultHttpErrorHandler`](api/java/play/http/DefaultHttpErrorHandler.html) to see what methods are available to override, and how you can take advantage of them. diff --git a/manual/working/javaGuide/main/application/JavaEssentialAction.md b/manual/working/javaGuide/main/application/JavaEssentialAction.md new file mode 100644 index 00000000..76028b3c --- /dev/null +++ b/manual/working/javaGuide/main/application/JavaEssentialAction.md @@ -0,0 +1,68 @@ + +# Introduction to Play HTTP API + +## What is EssentialAction? + +[`EssentialAction`](api/java/play/mvc/EssentialAction.html) is the underlying functional type used by Play's HTTP APIs. This differs from the `Action` type in Java, a higher-level type that accepts a `Context` and returns a `CompletionStage`. Most of the time you will not need to use `EssentialAction` directly in a Java application, but it can be useful when writing filters or interacting with other low-level Play APIs. + +To understand `EssentialAction` we need to understand the Play architecture. + +The core of Play is really small, surrounded by a fair amount of useful APIs, services and structure to make Web Programming tasks easier. + +Basically, Play's action API abstractly has the following type: + +```java +RequestHeader -> byte[] -> Result +``` + +The above takes the request header `RequestHeader`, then takes the request body as `byte[]` and produces a `Result`. + +Now this type presumes putting request body entirely into memory (or disk), even if you only want to compute a value out of it, or better forward it to a storage service like Amazon S3. + +We rather want to receive request body chunks as a stream and be able to process them progressively if necessary. + +What we need to change is the second arrow to make it receive its input in chunks and eventually produce a result. There is a type that does exactly this, it is called `Accumulator` and takes two type parameters. + +`Accumulator` is a type of [arrow](https://www.haskell.org/arrows/) that will take its input in chunks of type `E` and eventually return `R`. For our API we need an Accumulator that takes chunks of `ByteString` (essentially a more efficient wrapper for a byte array) and eventually return a `Result`. So we slightly modify the type to be: + +```java +RequestHeader -> Accumulator +``` + +Ultimately, our Java type looks like: + +```java +Function> +``` + +And this should read as: Take the request headers, take chunks of `ByteString` which represent the request body and eventually return a `Result`. This exactly how the `EssentialAction`'s apply method is defined: + +```java +public abstract Accumulator apply(RequestHeader requestHeader); +``` + +The `Result` type, on the other hand, can be abstractly thought of as the response headers and the body of the response: + +```java +Result(ResponseHeader header, ByteString body) +``` + +But, what if we want to send the response body progressively to the client without filling it entirely into memory? We need to improve our type. We need to replace the body type from a `ByteString` to something that produces chunks of `ByteString`. + +We already have a type for this and is called `Source` which means that it is capable of producing chunks of `E`, in our case `Source`: + +```java +Result(ResponseHeader header, Source body) +``` + +If we don't have to send the response progressively we still can send the entire body as a single chunk. In the actual API, Play supports different types of entities using the `HttpEntity` wrapper type, which supports streamed, chunked, and strict entities. + +## Bottom Line + +The essential Play HTTP API is quite simple: + +```java +RequestHeader -> Accumulator +``` + +which reads as the following: Take the `RequestHeader` then take chunks of `ByteString` and return a response. A response consists of `ResponseHeaders` and a body which is chunks of values convertible to `ByteString` to be written to the socket represented in the `Source` type. diff --git a/manual/working/javaGuide/main/application/JavaHttpFilters.md b/manual/working/javaGuide/main/application/JavaHttpFilters.md new file mode 100644 index 00000000..9a3efb3a --- /dev/null +++ b/manual/working/javaGuide/main/application/JavaHttpFilters.md @@ -0,0 +1,58 @@ + +# Filters + +Play provides a simple filter API for applying global filters to each request. + +## Filters vs action composition + +The filter API is intended for cross cutting concerns that are applied indiscriminately to all routes. For example, here are some common use cases for filters: + +* Logging/metrics collection +* [[GZIP encoding|GzipEncoding]] +* [[Security headers|SecurityHeaders]] + +In contrast, [[action composition|JavaActionsComposition]] is intended for route specific concerns, such as authentication and authorization, caching and so on. If your filter is not one that you want applied to every route, consider using action composition instead, it is far more powerful. And don't forget that you can create your own action builders that compose your own custom defined sets of actions to each route, to minimize boilerplate. + +## A simple logging filter + +The following is a simple filter that times and logs how long a request takes to execute in Play Framework, which implements the [`Filter`](api/java/play/mvc/Filter.html) trait: + +@[simple-filter](code/javaguide/application/httpfilters/LoggingFilter.java) + +Let's understand what's happening here. The first thing to notice is the signature of the `apply` method. The first parameter, `nextFilter`, is a function that takes a request header and produces a result. The second parameter, `requestHeader`, is the actual request header of the incoming request. + +The `nextFilter` parameter represents the next action in the filter chain. Invoking it will cause the action to be invoked. In most cases you will want to invoke this at some point in your future. You may decide to not invoke it if for some reason you want to block the request. + +We save a timestamp before invoking the next filter in the chain. Invoking the next filter returns a `CompletionStage` that will redeemed eventually. Take a look at the [[Handling asynchronous results|JavaAsync]] chapter for more details on asynchronous results. We then manipulate the `Result` in the future by calling the `thenApply` method with a closure that takes a `Result`. We calculate the time it took for the request, log it and send it back to the client in the response headers by calling `result.withHeader("Request-Time", "" + requestTime)`. + +## Using filters + +The simplest way to use a filter is to provide an implementation of the [`HttpFilters`](api/java/play/http/HttpFilters.html) interface in the root package called `Filters`. Typically you should extend the [`DefaultHttpFilters`](api/java/play/http/DefaultHttpFilters.html) class and pass your filters to the varargs constructor: + +@[filters](code/javaguide/application/httpfilters/Filters.java) + +If you want to have different filters in different environments, or would prefer not putting this class in the root package, you can configure where Play should find the class by setting `play.http.filters` in `application.conf` to the fully qualified class name of the class. For example: + + play.http.filters=com.example.Filters + +## Where do filters fit in? + +Filters wrap the action after the action has been looked up by the router. This means you cannot use a filter to transform a path, method or query parameter to impact the router. However you can direct the request to a different action by invoking that action directly from the filter, though be aware that this will bypass the rest of the filter chain. If you do need to modify the request before the router is invoked, a better way to do this would be to place your logic in [[ a `HttpRequestHandler`|JavaActionCreator#HTTP-request-handlers]] instead. + +Since filters are applied after routing is done, it is possible to access routing information from the request, via the `attrs` map on the `RequestHeader`. For example, you might want to log the time against the action method. In that case, you might update the filter to look like this: + +@[routing-info-access](code/javaguide/application/httpfilters/RoutedLoggingFilter.java) + +> **Note:** Routing attributes are a feature of the Play router. If you use a custom router these parameters may not be available. + +## More powerful filters + +Play provides a lower level filter API called [`EssentialFilter`](api/java/play/mvc/EssentialFilter.html) that gives you full access to the body of the request. This API allows you to wrap [[EssentialAction|JavaEssentialAction]] with another action. + +Here is the above filter example rewritten as an `EssentialFilter`: + +@[essential-filter-example](code/javaguide/application/httpfilters/EssentialLoggingFilter.java) + +The key difference here, apart from creating a new `EssentialAction` to wrap the passed in `next` action, is when we invoke next, we get back an [`Accumulator`](api/java/play/libs/streams/Accumulator.html). You could compose this with an Akka Streams Flow using the `through` method some transformations to the stream if you wished. We then `map` the result of the iteratee and thus handle it. + +> **Note:** Although it may seem that there are two different filter APIs, there is only one, `EssentialFilter`. The simpler `Filter` API in the earlier examples extends `EssentialFilter`, and implements it by creating a new `EssentialAction`. The passed in callback makes it appear to skip the body parsing by creating a promise for the `Result`, while the body parsing and the rest of the action are executed asynchronously. diff --git a/manual/working/javaGuide/main/application/code/javaguide/application/def/ErrorHandler.java b/manual/working/javaGuide/main/application/code/javaguide/application/def/ErrorHandler.java new file mode 100644 index 00000000..9a25e73d --- /dev/null +++ b/manual/working/javaGuide/main/application/code/javaguide/application/def/ErrorHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.application.def; + +// #default +import com.typesafe.config.Config; + +import play.*; +import play.api.OptionalSourceMapper; +import play.api.UsefulException; +import play.api.routing.Router; +import play.http.DefaultHttpErrorHandler; +import play.mvc.Http.*; +import play.mvc.*; + +import javax.inject.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +@Singleton +public class ErrorHandler extends DefaultHttpErrorHandler { + + @Inject + public ErrorHandler( + Config config, + Environment environment, + OptionalSourceMapper sourceMapper, + Provider routes) { + super(config, environment, sourceMapper, routes); + } + + protected CompletionStage onProdServerError( + RequestHeader request, UsefulException exception) { + return CompletableFuture.completedFuture( + Results.internalServerError("A server error occurred: " + exception.getMessage())); + } + + protected CompletionStage onForbidden(RequestHeader request, String message) { + return CompletableFuture.completedFuture( + Results.forbidden("You're not allowed to access this resource.")); + } +} +// #default diff --git a/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/EssentialLoggingFilter.java b/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/EssentialLoggingFilter.java new file mode 100644 index 00000000..9a589b41 --- /dev/null +++ b/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/EssentialLoggingFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.application.httpfilters; + +// #essential-filter-example +import java.util.concurrent.Executor; +import javax.inject.Inject; +import akka.util.ByteString; +import play.Logger; +import play.libs.streams.Accumulator; +import play.mvc.*; + +public class EssentialLoggingFilter extends EssentialFilter { + + private final Executor executor; + + @Inject + public EssentialLoggingFilter(Executor executor) { + super(); + this.executor = executor; + } + + @Override + public EssentialAction apply(EssentialAction next) { + return EssentialAction.of( + request -> { + long startTime = System.currentTimeMillis(); + Accumulator accumulator = next.apply(request); + return accumulator.map( + result -> { + long endTime = System.currentTimeMillis(); + long requestTime = endTime - startTime; + + Logger.info( + "{} {} took {}ms and returned {}", + request.method(), + request.uri(), + requestTime, + result.status()); + + return result.withHeader("Request-Time", "" + requestTime); + }, + executor); + }); + } +} +// #essential-filter-example diff --git a/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/Filters.java b/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/Filters.java new file mode 100644 index 00000000..905754e0 --- /dev/null +++ b/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/Filters.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.application.httpfilters; + +// #filters +import play.http.DefaultHttpFilters; +import play.filters.gzip.GzipFilter; +import javax.inject.Inject; + +public class Filters extends DefaultHttpFilters { + @Inject + public Filters(GzipFilter gzip, LoggingFilter logging) { + super(gzip, logging); + } +} +// #filters diff --git a/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/LoggingFilter.java b/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/LoggingFilter.java new file mode 100644 index 00000000..180a543f --- /dev/null +++ b/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/LoggingFilter.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.application.httpfilters; + +// #simple-filter +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import javax.inject.Inject; +import akka.stream.Materializer; +import play.Logger; +import play.mvc.*; + +public class LoggingFilter extends Filter { + + @Inject + public LoggingFilter(Materializer mat) { + super(mat); + } + + @Override + public CompletionStage apply( + Function> nextFilter, + Http.RequestHeader requestHeader) { + long startTime = System.currentTimeMillis(); + return nextFilter + .apply(requestHeader) + .thenApply( + result -> { + long endTime = System.currentTimeMillis(); + long requestTime = endTime - startTime; + + Logger.info( + "{} {} took {}ms and returned {}", + requestHeader.method(), + requestHeader.uri(), + requestTime, + result.status()); + + return result.withHeader("Request-Time", "" + requestTime); + }); + } +} +// #simple-filter diff --git a/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/RoutedLoggingFilter.java b/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/RoutedLoggingFilter.java new file mode 100644 index 00000000..f8b20f02 --- /dev/null +++ b/manual/working/javaGuide/main/application/code/javaguide/application/httpfilters/RoutedLoggingFilter.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.application.httpfilters; + +// #routing-info-access +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.Map; +import javax.inject.Inject; +import akka.stream.Materializer; +import play.Logger; +import play.api.routing.HandlerDef; +import play.mvc.*; +import play.routing.Router; + +public class RoutedLoggingFilter extends Filter { + + @Inject + public RoutedLoggingFilter(Materializer mat) { + super(mat); + } + + @Override + public CompletionStage apply( + Function> nextFilter, + Http.RequestHeader requestHeader) { + long startTime = System.currentTimeMillis(); + return nextFilter + .apply(requestHeader) + .thenApply( + result -> { + HandlerDef handlerDef = requestHeader.attrs().get(Router.Attrs.HANDLER_DEF); + String actionMethod = handlerDef.controller() + "." + handlerDef.method(); + long endTime = System.currentTimeMillis(); + long requestTime = endTime - startTime; + + Logger.info( + "{} took {}ms and returned {}", actionMethod, requestTime, result.status()); + + return result.withHeader("Request-Time", "" + requestTime); + }); + } +} +// #routing-info-access diff --git a/manual/working/javaGuide/main/application/code/javaguide/application/root/ErrorHandler.java b/manual/working/javaGuide/main/application/code/javaguide/application/root/ErrorHandler.java new file mode 100644 index 00000000..b026d3f3 --- /dev/null +++ b/manual/working/javaGuide/main/application/code/javaguide/application/root/ErrorHandler.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.application.root; + +// #root +import play.http.HttpErrorHandler; +import play.mvc.*; +import play.mvc.Http.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import javax.inject.Singleton; + +@Singleton +public class ErrorHandler implements HttpErrorHandler { + public CompletionStage onClientError( + RequestHeader request, int statusCode, String message) { + return CompletableFuture.completedFuture( + Results.status(statusCode, "A client error occurred: " + message)); + } + + public CompletionStage onServerError(RequestHeader request, Throwable exception) { + return CompletableFuture.completedFuture( + Results.internalServerError("A server error occurred: " + exception.getMessage())); + } +} +// #root diff --git a/manual/working/javaGuide/main/application/index.toc b/manual/working/javaGuide/main/application/index.toc new file mode 100644 index 00000000..d3399f3f --- /dev/null +++ b/manual/working/javaGuide/main/application/index.toc @@ -0,0 +1,4 @@ +JavaApplication:Application settings +JavaEssentialAction:Essential Actions +JavaHttpFilters:HTTP filters +JavaErrorHandling:Error handling diff --git a/manual/working/javaGuide/main/async/JavaAsync.md b/manual/working/javaGuide/main/async/JavaAsync.md index b2ab37c5..773adb13 100644 --- a/manual/working/javaGuide/main/async/JavaAsync.md +++ b/manual/working/javaGuide/main/async/JavaAsync.md @@ -1,111 +1,81 @@ - - # Handling asynchronous results ---> -# 非同期レスポンスの処理 - -## コントローラを非同期にする - -内部的には、Play Framework は根本から非同期です。Play はあらゆるリクエストを非同期かつノンブロッキングに取り扱います。 - -デフォルトの設定は非同期なコントローラに最適化されています。別の言い方をすると、アプリケーションコードは、例えばコントローラのコードで命令の実行を待ってしまうなど、ブロッキングするコントローラを避けなければなりません。ブロッキング操作の一般的な例は、JDBC 呼び出し、streaming API、HTTP リクエスト、そして長時間の計算です。 - -ブロッキングコントローラでもより多くのリクエストを並列に処理できるよう、デフォルト実行コンテキストのスレッド数を増強することもできますが、推奨されるアプローチに従ってコントローラを非同期に保つことで、容易にスケールし、負荷時でもシステム応答を維持することができます。 - -## ノンブロッキングなアクションの作成 - -Play では、例えばノンブロッキングなどのように、アクションコードはできる限り速くなければなりません。それでは、まだ処理結果を計算できていない場合にアクションは何を返すべきでしょうか? Result の *promsie* を返すべきです! - -`Promise` は、最終的に `Result` 型の値に置き換えられます。通常の `Result` の代わりに `Promise` を使うことで、なにもブロックせず速やかにアクションからリターンすることができます。その後、promise が置き換え可能になり次第、Play は Result を提供します。 +Java 8 provides a generic promise API called [`CompletionStage`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html). A `CompletionStage` will eventually be redeemed with a value of type `Result`. By using a `CompletionStage` instead of a normal `Result`, we are able to return from our action quickly without blocking anything. Play will then serve the result as soon as the promise is redeemed. - -レスポンスを待つ間に web クライアントはブロックされますが、サーバではなにもブロックされず、他のクライアントにサービスを提供するためにリソースを使うことできます。 +## How to create a `CompletionStage` - -## `Promise` の生成 - - -`Promise` を生成するためには、まず別の promise が必要です: この promise は Result を計算するために必要な実際の値を提供します。 +To create a `CompletionStage` we need another promise first: the promise that will give us the actual value we need to compute the result: @[promise-pi](code/javaguide/async/JavaAsync.java) - -Play の非同期 API メソッドは `Promise` を提供します。これは、`play.libs.WS` API を使って外部の Web サービスを呼び出す場合や、`play.libs.Akka` を使って Akka を使った非同期タスクを実行したり、アクターと通信したりする場合などが該当します。 +Play asynchronous API methods give you a `CompletionStage`. This is the case when you are calling an external web service using the `play.libs.WS` API, or if you are using Akka to schedule asynchronous tasks or to communicate with Actors using `play.libs.Akka`. + +In this case, using `CompletionStage.thenApply` will execute the completion stage in the same calling thread as the previous task. This is fine when you have a small amount of CPU bound logic with no blocking. - -`promise()` ヘルパーを使うと、簡単にコードブロックを非同期で実行して `Promise` を取得できます: +A simple way to execute a block of code asynchronously and to get a `CompletionStage` is to use the `CompletionStage.supplyAsync()` method: @[promise-async](code/javaguide/async/JavaAsync.java) - -> **注意:** どのスレッドコードがどの promise を実行しているか理解することが重要です。ここでは、集中的な計算は別のスレッドで実行されることになります。 -> -> 同期 IO を `Promise` で包んで魔法のように非同期にすることはできません。ブロッキング操作を避けるようアプリケーションアーキテクチャを変更できなければ、様々な場所でこの操作が実行され、スレッドがブロックされます。このため、操作を `Promsie` で包むだけではなく、期待する並列処理を取り扱うために充分なスレッドが設定されている別の実行コンテキストで実行されるよう、この操作を設定する必要があります。詳しくは [[Play のスレッドプールを理解する|ThreadPools]] を参照してください。 -> -> ブロッキング操作に Actor を使うのも便利です。Actor はタイムアウトおよび失敗の取り扱い、ブロッキング実行コンテキストの設定、そしてサービスに関連し得るあらゆる状態の管理のための、洗練されたモデルを備えています。また、Actor は同時に発生するキャッシュおよびデータベースへのリクエストに対応し、バックエンドサーバのクラスタ上でリモート実行できる `ScatterGatherFirstCompletedRouter` パターンを備えています。ただし、Actor は要件に対してやり過ぎかも知れません。 - - -## 非同期な Result - - -あとから作成される `Result` を返すことになります。非同期に Result を返すため、アクションは `Promise` を返す必要があります: - -@[async](code/javaguide/async/controllers/Application.java) - - -## アクションはデフォルトで非同期 - -Play の [[actions|JavaActions]] はデフォルトで非同期です。例えば、以下のコントローラコードで返される `Result` は、内部的には promise に包まれています: @[simple-action](../http/code/javaguide/http/JavaActions.java) - -> **注意:** アクションコードが `Result` または `Promise` のどちらを返そうとも、返却される各種オブジェクトは内部的に同じように取り扱われます。ただ一種類の非同期な `Action` があるだけで、(同期的なものと非同期的なものの) 二種類があるわけではありません。`Promise` を返却するのは、ノンブロッキングなコードを書くためのテクニックです。 \ No newline at end of file +> **Note:** Whether the action code returns a `Result` or a `CompletionStage`, both kinds of returned object are handled internally in the same way. There is a single kind of `Action`, which is asynchronous, and not two kinds (a synchronous one and an asynchronous one). Returning a `CompletionStage` is a technique for writing non-blocking code. + +## Handling time-outs + +It is often useful to handle time-outs properly, to avoid having the web browser block and wait if something goes wrong. You can use the [`play.libs.concurrent.Futures.timeout`](api/java/play/libs/concurrent/Futures.html) method to wrap a `CompletionStage` in a non-blocking timeout. + +@[timeout](code/javaguide/async/JavaAsync.java) + +> **Note:** Timeout is not the same as cancellation -- even in case of timeout, the given future will still complete, even though that completed value is not returned. + diff --git a/manual/working/javaGuide/main/async/JavaComet.md b/manual/working/javaGuide/main/async/JavaComet.md index a68b5f77..f9f1f9ee 100644 --- a/manual/working/javaGuide/main/async/JavaComet.md +++ b/manual/working/javaGuide/main/async/JavaComet.md @@ -1,33 +1,37 @@ - -# Comet sockets + +# Comet -## Using chunked responses to create Comet sockets +## Using chunked responses with Comet -An useful usage of **Chunked responses** is to create Comet sockets. A Comet socket is just a chunked `text/html` response containing only `")); - assertThat(content, containsString("")); - assertThat(content, containsString("")); - } +public class JavaComet extends WithApplication { - public static class Controller1 extends MockJavaAction { - //#manual - public static Result index() { - // Prepare a chunked text stream - Chunks chunks = StringChunks.whenReady(out -> { - out.write(""); - out.write(""); - out.write(""); - out.close(); - }); + public static class Controller1 extends MockJavaAction { - response().setContentType("text/html"); - return ok(chunks); - } - //#manual + Controller1(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); } - @Test - public void comet() { - String content = contentAsString(MockJavaActionHelper.call(new Controller2(), fakeRequest())); - assertThat(content, containsString("")); - assertThat(content, containsString("")); - assertThat(content, containsString("")); + // #comet-string + public static Result index() { + final Source source = Source.from(Arrays.asList("kiki", "foo", "bar")); + return ok().chunked(source.via(Comet.string("parent.cometMessage"))).as(Http.MimeTypes.HTML); } + // #comet-string + } - public static class Controller2 extends MockJavaAction { - //#comet - public Result index() { - return ok(Comet.whenConnected("console.log", comet -> { - comet.sendMessage("kiki"); - comet.sendMessage("foo"); - comet.sendMessage("bar"); - comet.close(); - })); - } - //#comet - } + public static class Controller2 extends MockJavaAction { - @Test - public void foreverIframe() { - String content = contentAsString(MockJavaActionHelper.call(new Controller3(), fakeRequest())); - assertThat(content, containsString("")); - assertThat(content, containsString("")); - assertThat(content, containsString("")); + Controller2(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); } - public static class Controller3 extends MockJavaAction { - //#forever-iframe - public Result index() { - return ok(Comet.whenConnected("parent.cometMessage", comet -> { - comet.sendMessage("kiki"); - comet.sendMessage("foo"); - comet.sendMessage("bar"); - comet.close(); - })); - } - //#forever-iframe + // #comet-json + public static Result index() { + final ObjectNode objectNode = Json.newObject(); + objectNode.put("foo", "bar"); + final Source source = Source.from(Collections.singletonList(objectNode)); + return ok().chunked(source.via(Comet.json("parent.cometMessage"))).as(Http.MimeTypes.HTML); } + // #comet-json + } + + @Test + public void foreverIframe() { + String content = + contentAsString( + MockJavaActionHelper.call( + new Controller1(app.injector().instanceOf(JavaHandlerComponents.class)), + fakeRequest(), + mat), + mat); + assertThat( + content, + containsString("")); + assertThat( + content, + containsString("")); + assertThat( + content, + containsString("")); + } + + @Test + public void foreverIframeWithJson() { + String content = + contentAsString( + MockJavaActionHelper.call( + new Controller2(app.injector().instanceOf(JavaHandlerComponents.class)), + fakeRequest(), + mat), + mat); + assertThat( + content, + containsString( + "")); + } } diff --git a/manual/working/javaGuide/main/async/code/javaguide/async/JavaStream.java b/manual/working/javaGuide/main/async/code/javaguide/async/JavaStream.java index 20a00d79..667ed32e 100644 --- a/manual/working/javaGuide/main/async/code/javaguide/async/JavaStream.java +++ b/manual/working/javaGuide/main/async/code/javaguide/async/JavaStream.java @@ -1,117 +1,242 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.async; +import akka.actor.Status; +import akka.stream.javadsl.FileIO; +import akka.util.ByteString; +import akka.stream.javadsl.Source; +import akka.stream.OverflowStrategy; +import akka.NotUsed; + import javaguide.testhelpers.MockJavaAction; -import javaguide.testhelpers.MockJavaActionHelper; import org.apache.commons.io.IOUtils; -import org.junit.Before; import org.junit.Test; +import play.core.j.JavaHandlerComponents; +import play.http.HttpEntity; +import play.mvc.ResponseHeader; import play.mvc.Result; -import play.mvc.Results.Chunks; import play.test.WithApplication; import java.io.*; +import java.util.Collections; +import java.util.Optional; +import static javaguide.testhelpers.MockJavaActionHelper.call; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static play.test.Helpers.*; public class JavaStream extends WithApplication { - @Test - public void byDefault() { - assertThat(contentAsString(MockJavaActionHelper.call(new Controller1(), fakeRequest())), equalTo("Hello World")); - } - - public static class Controller1 extends MockJavaAction { - //#by-default - public Result index() { - return ok("Hello World"); - } - //#by-default - } - - @Test - public void serveFile() throws Exception { - File file = new File("/tmp/fileToServe.pdf"); - file.deleteOnExit(); - OutputStream os = new FileOutputStream(file); - try { - IOUtils.write("hi", os); - } finally { - os.close(); - } - Result result = MockJavaActionHelper.call(new Controller2(), fakeRequest()); - assertThat(contentAsString(result), equalTo("hi")); - assertThat(header(CONTENT_LENGTH, result), equalTo("2")); - file.delete(); - } - - public static class Controller2 extends MockJavaAction { - //#serve-file - public Result index() { - return ok(new java.io.File("/tmp/fileToServe.pdf")); - } - //#serve-file - } - - @Test - public void inputStream() { - String content = contentAsString(MockJavaActionHelper.call(new Controller3(), fakeRequest())); - // Wait until results refactoring is merged, then this will work - // assertThat(content, containsString("hello")); - } - - private static InputStream getDynamicStreamSomewhere() { - return new ByteArrayInputStream("hello".getBytes()); - } - - public static class Controller3 extends MockJavaAction { - //#input-stream - public Result index() { - InputStream is = getDynamicStreamSomewhere(); - return ok(is); - } - //#input-stream - } - - @Test - public void chunked() { - String content = contentAsString(MockJavaActionHelper.call(new Controller4(), fakeRequest())); - assertThat(content, equalTo( - "4\r\n" + - "kiki\r\n" + - "3\r\n" + - "foo\r\n" + - "3\r\n" + - "bar\r\n" + - "0\r\n\r\n" - )); - } - - public static class Controller4 extends MockJavaAction { - //#chunked - public Result index() { - // Prepare a chunked text stream - Chunks chunks = StringChunks.whenReady( - JavaStream::registerOutChannelSomewhere - ); - - // Serves this stream with 200 OK - return ok(chunks); - } - //#chunked - } - - //#register-out-channel - public static void registerOutChannelSomewhere(Chunks.Out out) { - out.write("kiki"); - out.write("foo"); - out.write("bar"); - out.close(); - } - //#register-out-channel + @Test + public void byDefault() { + assertThat( + contentAsString( + call(new Controller1(instanceOf(JavaHandlerComponents.class)), fakeRequest(), mat)), + equalTo("Hello World")); + } + + public static class Controller1 extends MockJavaAction { + + Controller1(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); + } + + // #by-default + public Result index() { + return ok("Hello World"); + } + // #by-default + } + + @Test + public void byDefaultWithHttpEntity() { + assertThat( + contentAsString( + call( + new ControllerWithHttpEntity(instanceOf(JavaHandlerComponents.class)), + fakeRequest(), + mat)), + equalTo("Hello World")); + } + + public static class ControllerWithHttpEntity extends MockJavaAction { + + ControllerWithHttpEntity(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); + } + + // #by-default-http-entity + public Result index() { + return new Result( + new ResponseHeader(200, Collections.emptyMap()), + new HttpEntity.Strict(ByteString.fromString("Hello World"), Optional.of("text/plain"))); + } + // #by-default-http-entity + } + + public static class ControllerStreamingFile extends MockJavaAction { + + ControllerStreamingFile(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); + } + private void createSourceFromFile() { + // #create-source-from-file + java.io.File file = new java.io.File("/tmp/fileToServe.pdf"); + java.nio.file.Path path = file.toPath(); + Source source = FileIO.fromPath(path); + // #create-source-from-file + } + + // #streaming-http-entity + public Result index() { + java.io.File file = new java.io.File("/tmp/fileToServe.pdf"); + java.nio.file.Path path = file.toPath(); + Source source = FileIO.fromPath(path); + + return new Result( + new ResponseHeader(200, Collections.emptyMap()), + new HttpEntity.Streamed(source, Optional.empty(), Optional.of("text/plain"))); + } + // #streaming-http-entity + } + + public static class ControllerStreamingFileWithContentLength extends MockJavaAction { + + ControllerStreamingFileWithContentLength(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); + } + + // #streaming-http-entity-with-content-length + public Result index() { + java.io.File file = new java.io.File("/tmp/fileToServe.pdf"); + java.nio.file.Path path = file.toPath(); + Source source = FileIO.fromPath(path); + + Optional contentLength = Optional.of(file.length()); + + return new Result( + new ResponseHeader(200, Collections.emptyMap()), + new HttpEntity.Streamed(source, contentLength, Optional.of("text/plain"))); + } + // #streaming-http-entity-with-content-length + } + + @Test + public void serveFile() throws Exception { + File file = new File("/tmp/fileToServe.pdf"); + file.deleteOnExit(); + try (OutputStream os = java.nio.file.Files.newOutputStream(file.toPath())) { + IOUtils.write("hi", os, "UTF-8"); + } + Result result = + call(new Controller2(instanceOf(JavaHandlerComponents.class)), fakeRequest(), mat); + assertThat(contentAsString(result, mat), equalTo("hi")); + assertThat(result.body().contentLength(), equalTo(Optional.of(2L))); + file.delete(); + } + + public static class Controller2 extends MockJavaAction { + + Controller2(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); + } + + // #serve-file + public Result index() { + return ok(new java.io.File("/tmp/fileToServe.pdf")); + } + // #serve-file + } + + public static class ControllerServeFileWithName extends MockJavaAction { + + ControllerServeFileWithName(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); + } + + // #serve-file-with-name + public Result index() { + return ok(new java.io.File("/tmp/fileToServe.pdf"), "fileToServe.pdf"); + } + // #serve-file-with-name + } + + public static class ControllerServeAttachment extends MockJavaAction { + + ControllerServeAttachment(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); + } + + // #serve-file-attachment + public Result index() { + return ok(new java.io.File("/tmp/fileToServe.pdf"), /*inline = */ false); + } + // #serve-file-attachment + } + + @Test + public void inputStream() { + String content = + contentAsString( + call(new Controller3(instanceOf(JavaHandlerComponents.class)), fakeRequest(), mat), + mat); + // Wait until results refactoring is merged, then this will work + // assertThat(content, containsString("hello")); + } + + private static InputStream getDynamicStreamSomewhere() { + return new ByteArrayInputStream("hello".getBytes()); + } + + public static class Controller3 extends MockJavaAction { + + Controller3(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); + } + + // #input-stream + public Result index() { + InputStream is = getDynamicStreamSomewhere(); + return ok(is); + } + // #input-stream + } + + @Test + public void chunked() { + String content = + contentAsString( + call(new Controller4(instanceOf(JavaHandlerComponents.class)), fakeRequest(), mat), + mat); + assertThat(content, equalTo("kikifoobar")); + } + + public static class Controller4 extends MockJavaAction { + + Controller4(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); + } + + // #chunked + public Result index() { + // Prepare a chunked text stream + Source source = + Source.actorRef(256, OverflowStrategy.dropNew()) + .mapMaterializedValue( + sourceActor -> { + sourceActor.tell(ByteString.fromString("kiki"), null); + sourceActor.tell(ByteString.fromString("foo"), null); + sourceActor.tell(ByteString.fromString("bar"), null); + sourceActor.tell(new Status.Success(NotUsed.getInstance()), null); + return NotUsed.getInstance(); + }); + // Serves this stream with 200 OK + return ok().chunked(source); + } + // #chunked + } } diff --git a/manual/working/javaGuide/main/async/code/javaguide/async/JavaWebSockets.java b/manual/working/javaGuide/main/async/code/javaguide/async/JavaWebSockets.java index 3edd8d74..b06ef8c8 100644 --- a/manual/working/javaGuide/main/async/code/javaguide/async/JavaWebSockets.java +++ b/manual/working/javaGuide/main/async/code/javaguide/async/JavaWebSockets.java @@ -1,105 +1,162 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.async; -//#imports import akka.actor.*; -import play.libs.F.*; +import akka.stream.Materializer; +import play.libs.streams.ActorFlow; import play.mvc.WebSocket; -//#imports +import play.libs.F; + +// #streams-imports +import akka.stream.javadsl.*; +// #streams-imports import play.mvc.Controller; import java.io.Closeable; -import com.fasterxml.jackson.databind.JsonNode; +import java.util.concurrent.CompletableFuture; public class JavaWebSockets { - public static class ActorController1 { + public static class Actor1 extends AbstractActor { + private final Closeable someResource; - //#actor-accept - public static WebSocket socket() { - return WebSocket.withActor(MyWebSocketActor::props); - } - //#actor-accept + public Actor1(Closeable someResource) { + this.someResource = someResource; } - public static class Actor1 extends UntypedActor { - private final Closeable someResource; - - public Actor1(Closeable someResource) { - this.someResource = someResource; - } - - public void onReceive(Object message) throws Exception { - } - - //#actor-post-stop - public void postStop() throws Exception { - someResource.close(); - } - //#actor-post-stop + @Override + public Receive createReceive() { + return receiveBuilder() + // match() messages here... + .build(); } - public static class Actor2 extends UntypedActor { - public void onReceive(Object message) throws Exception { - } + // #actor-post-stop + public void postStop() throws Exception { + someResource.close(); + } + // #actor-post-stop + } + + public static class Actor2 extends AbstractActor { + @Override + public Receive createReceive() { + return receiveBuilder() + // match() messages here + .build(); + } - { - //#actor-stop - self().tell(PoisonPill.getInstance(), self()); - //#actor-stop - } + { + // #actor-stop + self().tell(PoisonPill.getInstance(), self()); + // #actor-stop } + } + + public static class ActorController2 extends Controller { + private ActorSystem actorSystem; + private Materializer materializer; - public static class ActorController2 extends Controller { - //#actor-reject - public WebSocket socket() { + // #actor-reject + public WebSocket socket() { + return WebSocket.Text.acceptOrResult( + request -> { if (session().get("user") != null) { - return WebSocket.withActor(MyWebSocketActor::props); + return CompletableFuture.completedFuture( + F.Either.Right( + ActorFlow.actorRef(MyWebSocketActor::props, actorSystem, materializer))); } else { - return WebSocket.reject(forbidden()); + return CompletableFuture.completedFuture(F.Either.Left(forbidden())); } - } - //#actor-reject + }); } + // #actor-reject + } - public static class ActorController4 extends Controller { - //#actor-json - public WebSocket socket() { - return WebSocket.withActor(MyWebSocketActor::props); - } - //#actor-json + public static class ActorController4 extends Controller { + private ActorSystem actorSystem; + private Materializer materializer; + + // #actor-json + public WebSocket socket() { + return WebSocket.Json.accept( + request -> ActorFlow.actorRef(MyWebSocketActor::props, actorSystem, materializer)); } + // #actor-json + } - // No simple way to test websockets yet + public static class InEvent {} - public static class Controller1 { - //#websocket - public WebSocket socket() { - return WebSocket.whenReady((in, out) -> { - // For each event received on the socket, - in.onMessage(System.out::println); + public static class OutEvent {} - // When the socket is closed. - in.onClose(() -> System.out.println("Disconnected")); + public static class ActorController5 extends Controller { + private ActorSystem actorSystem; + private Materializer materializer; - // Send a single 'Hello!' message - out.write("Hello!"); - }); - } - //#websocket + // #actor-json-class + public WebSocket socket() { + return WebSocket.json(InEvent.class) + .accept( + request -> ActorFlow.actorRef(MyWebSocketActor::props, actorSystem, materializer)); + } + // #actor-json-class + } + + public static class Controller1 extends Controller { + // #streams1 + public WebSocket socket() { + return WebSocket.Text.accept( + request -> { + // Log events to the console + Sink in = Sink.foreach(System.out::println); + + // Send a single 'Hello!' message and then leave the socket open + Source out = Source.single("Hello!").concat(Source.maybe()); + + return Flow.fromSinkAndSource(in, out); + }); } + // #streams1 + } + + public static class Controller2 extends Controller { + + // #streams2 + public WebSocket socket() { + return WebSocket.Text.accept( + request -> { + // Just ignore the input + Sink in = Sink.ignore(); + + // Send a single 'Hello!' message and close + Source out = Source.single("Hello!"); + + return Flow.fromSinkAndSource(in, out); + }); + } + // #streams2 + + } + + public static class Controller3 extends Controller { + + // #streams3 + public WebSocket socket() { + return WebSocket.Text.accept( + request -> { - public static class Controller2 { - //#discard-input - public WebSocket socket() { - return WebSocket.whenReady((in, out) -> { - out.write("Hello!"); - out.close(); - }); - } - //#discard-input + // log the message to stdout and send response back to client + return Flow.create() + .map( + msg -> { + System.out.println(msg); + return "I received your message: " + msg; + }); + }); } + // #streams3 + } } diff --git a/manual/working/javaGuide/main/async/code/javaguide/async/MyWebSocketActor.java b/manual/working/javaGuide/main/async/code/javaguide/async/MyWebSocketActor.java index 46b669c2..f85fa1ed 100644 --- a/manual/working/javaGuide/main/async/code/javaguide/async/MyWebSocketActor.java +++ b/manual/working/javaGuide/main/async/code/javaguide/async/MyWebSocketActor.java @@ -1,27 +1,28 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.async; -//#actor +// #actor import akka.actor.*; -public class MyWebSocketActor extends UntypedActor { +public class MyWebSocketActor extends AbstractActor { - public static Props props(ActorRef out) { - return Props.create(MyWebSocketActor.class, out); - } + public static Props props(ActorRef out) { + return Props.create(MyWebSocketActor.class, out); + } - private final ActorRef out; + private final ActorRef out; - public MyWebSocketActor(ActorRef out) { - this.out = out; - } + public MyWebSocketActor(ActorRef out) { + this.out = out; + } - public void onReceive(Object message) throws Exception { - if (message instanceof String) { - out.tell("I received your message: " + message, self()); - } - } + @Override + public Receive createReceive() { + return receiveBuilder() + .match(String.class, message -> out.tell("I received your message: " + message, self())) + .build(); + } } -//#actor +// #actor diff --git a/manual/working/javaGuide/main/async/code/javaguide/async/controllers/Application.java b/manual/working/javaGuide/main/async/code/javaguide/async/controllers/Application.java index e58e726d..49b14382 100644 --- a/manual/working/javaGuide/main/async/code/javaguide/async/controllers/Application.java +++ b/manual/working/javaGuide/main/async/code/javaguide/async/controllers/Application.java @@ -1,36 +1,39 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.async.controllers; import play.mvc.Result; -import play.libs.F.Function; -import play.libs.F.Function0; -import play.libs.F.Promise; import play.mvc.Controller; -//#async-explicit-ec-imports -import play.libs.HttpExecution; -import scala.concurrent.ExecutionContext; -//#async-explicit-ec-imports +// #async-explicit-ec-imports +import play.libs.concurrent.HttpExecution; + +import javax.inject.Inject; +import java.util.concurrent.Executor; +import java.util.concurrent.CompletionStage; +import static java.util.concurrent.CompletableFuture.supplyAsync; +// #async-explicit-ec-imports + +// #async-explicit-ec public class Application extends Controller { - //#async - public Promise index() { - return Promise.promise(() -> intensiveComputation()) - .map((Integer i) -> ok("Got result: " + i)); - } - //#async - private ExecutionContext myThreadPool = null; + private MyExecutionContext myExecutionContext; - //#async-explicit-ec - public Promise index2() { + @Inject + public Application(MyExecutionContext myExecutionContext) { + this.myExecutionContext = myExecutionContext; + } + + public CompletionStage index() { // Wrap an existing thread pool, using the context from the current thread - ExecutionContext myEc = HttpExecution.fromThread(myThreadPool); - return Promise.promise(() -> intensiveComputation(), myEc) - .map((Integer i) -> ok("Got result: " + i), myEc); + Executor myEc = HttpExecution.fromThread((Executor) myExecutionContext); + return supplyAsync(() -> intensiveComputation(), myEc) + .thenApplyAsync(i -> ok("Got result: " + i), myEc); } - //#async-explicit-ec - public int intensiveComputation() { return 2;} + public int intensiveComputation() { + return 2; + } } +// #async-explicit-ec diff --git a/manual/working/javaGuide/main/async/code/javaguide/async/controllers/MyExecutionContext.java b/manual/working/javaGuide/main/async/code/javaguide/async/controllers/MyExecutionContext.java new file mode 100644 index 00000000..17fbcb45 --- /dev/null +++ b/manual/working/javaGuide/main/async/code/javaguide/async/controllers/MyExecutionContext.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.async.controllers; + +import akka.actor.ActorSystem; +import play.libs.concurrent.CustomExecutionContext; + +import javax.inject.Inject; + +// #custom-execution-context +public class MyExecutionContext extends CustomExecutionContext { + + @Inject + public MyExecutionContext(ActorSystem actorSystem) { + // uses a custom thread pool defined in application.conf + super(actorSystem, "my.dispatcher"); + } +} +// #custom-execution-context diff --git a/manual/working/javaGuide/main/async/code/javaguide/async/websocket/HomeController.java b/manual/working/javaGuide/main/async/code/javaguide/async/websocket/HomeController.java new file mode 100644 index 00000000..642dbc17 --- /dev/null +++ b/manual/working/javaGuide/main/async/code/javaguide/async/websocket/HomeController.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.async.websocket; + +import javaguide.async.MyWebSocketActor; + +// #content +import play.libs.streams.ActorFlow; +import play.mvc.*; +import akka.actor.*; +import akka.stream.*; +import javax.inject.Inject; + +public class HomeController extends Controller { + + private final ActorSystem actorSystem; + private final Materializer materializer; + + @Inject + public HomeController(ActorSystem actorSystem, Materializer materializer) { + this.actorSystem = actorSystem; + this.materializer = materializer; + } + + public WebSocket socket() { + return WebSocket.Text.accept( + request -> ActorFlow.actorRef(MyWebSocketActor::props, actorSystem, materializer)); + } +} +// #content diff --git a/manual/working/javaGuide/main/async/index.toc b/manual/working/javaGuide/main/async/index.toc index 7c646a3b..55841223 100644 --- a/manual/working/javaGuide/main/async/index.toc +++ b/manual/working/javaGuide/main/async/index.toc @@ -1,4 +1,4 @@ -JavaAsync:非同期レスポンスの処理 -JavaStream:HTTP レスポンスのストリーミング +JavaAsync:Handling asynchronous results +JavaStream:Streaming HTTP responses JavaComet:Comet -JavaWebSockets:WebSocket \ No newline at end of file +JavaWebSockets:WebSockets diff --git a/manual/working/javaGuide/main/cache/JavaCache.md b/manual/working/javaGuide/main/cache/JavaCache.md index e80371b2..796dcf6c 100644 --- a/manual/working/javaGuide/main/cache/JavaCache.md +++ b/manual/working/javaGuide/main/cache/JavaCache.md @@ -1,30 +1,43 @@ - + # The Play cache API Caching data is a typical optimization in modern applications, and so Play provides a global cache. An important point about the cache is that it behaves just like a cache should: the data you just stored may just go missing. -For any data stored in the cache, a regeneration strategy needs to be put in place in case the data goes missing. This philosophy is one of the fundamentals behind Play, and is different from Java EE, where the session is expected to retain values throughout its lifetime. +For any data stored in the cache, a regeneration strategy needs to be put in place in case the data goes missing. This philosophy is one of the fundamentals behind Play, and is different from Java EE, where the session is expected to retain values throughout its lifetime. -The default implementation of the cache API uses [EHCache](http://www.ehcache.org/). +The default implementation of the cache API uses [Ehcache](http://www.ehcache.org/). ## Importing the Cache API -Add `cache` into your dependencies list. For example, in `build.sbt`: +Play provides both an API and an default Ehcache implementation of that API. To get the full Ehcache implementation, add `ehcache` to your dependencies list: -```scala -libraryDependencies ++= Seq( - cache, - ... -) -``` +@[ehcache-sbt-dependencies](code/cache.sbt) + +This will also automatically set up the bindings for runtime DI so the components are injectable. + +To add only the API, add `cacheApi` to your dependencies list. + +@[cache-sbt-dependencies](code/cache.sbt) + +The API dependency is useful if you'd like to define your own bindings for the `Cached` helper and `AsyncCacheApi`, etc., without having to depend on Ehcache. If you're writing a custom cache module you should use this. + +## JCache Support + +Ehcache implements the [JSR 107](https://github.com/jsr107/jsr107spec) specification, also known as JCache, but Play does not bind `javax.caching.CacheManager` by default. To bind `javax.caching.CacheManager` to the default provider, add the following to your dependencies list: + +@[jcache-sbt-dependencies](code/cache.sbt) + +If you are using Guice, you can add the following for Java annotations: + +@[jcache-guice-annotation-sbt-dependencies](code/cache.sbt) ## Accessing the Cache API -The cache API is provided by the [CacheApi](api/java/play/cache/CacheApi.html) object, and can be injected into your component like any other dependency. For example: +The cache API is defined by the [AsyncCacheApi](api/java/play/cache/AsyncCacheApi.html) and [SyncCacheApi](api/java/play/cache/SyncCacheApi.html) interfaces, depending on whether you want an asynchronous or synchronous implementation, and can be injected into your component like any other dependency. For example: @[inject](code/javaguide/cache/inject/Application.java) -> **Note:** The API is intentionally minimal to allow various implementations to be plugged in. If you need a more specific API, use the one provided by your Cache plugin. +> **Note:** The API is intentionally minimal to allow various implementations to be plugged in. If you need a more specific API, use the one provided by your Cache library. Using this simple API you can store data in the cache: @@ -42,27 +55,58 @@ You can also supply a `Callable` that generates stores the value if no value is @[get-or-else](code/javaguide/cache/JavaCache.java) +**Note**: `getOrElseUpdate` is not an atomic operation in Ehcache and is implemented as a `get` followed by computing the value from the `Callable`, then a `set`. This means it's possible for the value to be computed multiple times if multiple threads are calling `getOrElse` simultaneously. + To remove an item from the cache use the `remove` method: @[remove](code/javaguide/cache/JavaCache.java) +To remove all items from the cache use the `removeAll` method: + +@[removeAll](code/javaguide/cache/JavaCache.java) + +`removeAll()` is only available on `AsyncCacheApi`, since removing all elements of the cache is rarely something you want to do sychronously. The expectation is that removing all items from the cache should only be needed as an admin operation in special cases, not part of the normal operation of your app. + +Note that the [SyncCacheApi](api/java/play/cache/SyncCacheApi.html) has the same API, except it returns the values directly instead of using futures. + ## Accessing different caches -It is possible to access different caches. The default cache is called `play`, and can be configured by creating a file called `ehcache.xml`. Additional caches may be configured with different configurations, or even implementations. +It is possible to access different caches. In the default Ehcache implementation, the default cache is called `play`, and can be configured by creating a file called `ehcache.xml`. Additional caches may be configured with different configurations, or even implementations. If you want to access multiple different ehcache caches, then you'll need to tell Play to bind them in `application.conf`, like so: play.cache.bindCaches = ["db-cache", "user-cache", "session-cache"] +By default, Play will try to create these caches for you. If you would like to define them yourself in `ehcache.xml`, you can set: + + play.cache.createBoundCaches = false + Now to access these different caches, when you inject them, use the [NamedCache](api/java/play/cache/NamedCache.html) qualifier on your dependency, for example: @[qualified](code/javaguide/cache/qualified/Application.java) +## Setting the execution context + +By default, all Ehcache operations are blocking, and async implementations will block threads in the default execution context. Usually this is okay if you are using Play's default configuration, which only stores elements in memory since reads should be relatively fast. However, depending on how EhCache was configured and [where the data is stored](https://www.ehcache.org/generated/2.10.4/html/ehc-all/#page/Ehcache_Documentation_Set%2Fco-store_storage_tiers.html), this blocking I/O might be too costly. +For such a case you can configure a different [Akka dispatcher](https://doc.akka.io/docs/akka/current/dispatchers.html?language=scala#looking-up-a-dispatcher) and set it via `play.cache.dispatcher` so the EhCache plugin makes use of it: + +``` +play.cache.dispatcher = "contexts.blockingCacheDispatcher" + +contexts { + blockingCacheDispatcher { + fork-join-executor { + parallelism-factor = 3.0 + } + } +} +``` + ## Caching HTTP responses -You can easily create a smart cached action using standard `Action` composition. +You can easily create a smart cached action using standard `Action` composition. -> **Note:** Play HTTP `Result` instances are safe to cache and reuse later. +> **Tip:** Play HTTP `Result` instances are safe to cache and reuse later. Play provides a default built-in helper for the standard case: @@ -70,14 +114,16 @@ Play provides a default built-in helper for the standard case: ## Custom implementations -It is possible to provide a custom implementation of the [CacheApi](api/java/play/cache/CacheApi.html) that either replaces, or sits along side the default implementation. +It is possible to provide a custom implementation of the cache API that either replaces or sits alongside the default implementation. -To replace the default implementation, you'll need to disable the default implementation by setting the following in `application.conf`: +To replace the default implementation based on something other than Ehcache, you only need the `cacheApi` dependency rather than the `ehcache` dependency in your `build.sbt`. If you still need access to the Ehcache implementation classes, you can use `ehcache` and disable the module from automatically binding it in `application.conf`: ``` -play.modules.disabled += "play.api.cache.EhCacheModule" +play.modules.disabled += "play.api.cache.ehcache.EhCacheModule" ``` -Then simply implement CacheApi and bind it in the DI container. +You can then implement [AsyncCacheApi](api/java/play/cache/AsyncCacheApi.html) and bind it in the DI container. You can also bind [SyncCacheApi](api/java/play/cache/SyncCacheApi.html) to [DefaultSyncCacheApi](api/java/play/cache/DefaultSyncCacheApi.html), which simply wraps the async implementation. + +Note that the `removeAll` method may not be supported by your cache implementation, either because it is not possible or because it would be unnecessarily inefficient. If that is the case, you can throw an `UnsupportedOperationException` in the `removeAll` method. To provide an implementation of the cache API in addition to the default implementation, you can either create a custom qualifier, or reuse the `NamedCache` qualifier to bind the implementation. diff --git a/manual/working/javaGuide/main/cache/code/cache.sbt b/manual/working/javaGuide/main/cache/code/cache.sbt new file mode 100644 index 00000000..e5ff9ed2 --- /dev/null +++ b/manual/working/javaGuide/main/cache/code/cache.sbt @@ -0,0 +1,23 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + +//#cache-sbt-dependencies +libraryDependencies ++= Seq( + cacheApi +) +//#cache-sbt-dependencies + +//#ehcache-sbt-dependencies +libraryDependencies ++= Seq( + ehcache +) +//#ehcache-sbt-dependencies + +//#jcache-sbt-dependencies +libraryDependencies += jcache +//#jcache-sbt-dependencies + +//#jcache-guice-annotation-sbt-dependencies +libraryDependencies += "org.jsr107.ri" % "cache-annotations-ri-guice" % "1.0.0" +//#jcache-guice-annotation-sbt-dependencies diff --git a/manual/working/javaGuide/main/cache/code/javaguide/cache/JavaCache.java b/manual/working/javaGuide/main/cache/code/javaguide/cache/JavaCache.java index ef6e0f51..8ce58081 100644 --- a/manual/working/javaGuide/main/cache/code/javaguide/cache/JavaCache.java +++ b/manual/working/javaGuide/main/cache/code/javaguide/cache/JavaCache.java @@ -1,88 +1,129 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.cache; +import akka.Done; import com.google.common.collect.ImmutableMap; import org.junit.Test; import play.Application; -import play.cache.CacheApi; +import play.cache.AsyncCacheApi; import play.cache.Cached; +import play.core.j.JavaHandlerComponents; import play.mvc.*; import play.test.WithApplication; import javaguide.testhelpers.MockJavaAction; -import javaguide.testhelpers.MockJavaActionHelper; -import java.util.Arrays; -import java.util.concurrent.Callable; +import java.lang.Throwable; +import java.util.Collections; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CompletableFuture; +import static javaguide.testhelpers.MockJavaActionHelper.call; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static play.test.Helpers.*; public class JavaCache extends WithApplication { - @Override - protected Application provideApplication() { - return fakeApplication(ImmutableMap.of("play.cache.bindCaches", Arrays.asList("session-cache"))); - } + @Override + protected Application provideApplication() { + return fakeApplication( + ImmutableMap.of("play.cache.bindCaches", Collections.singletonList("session-cache"))); + } + + private class News {} - public class News {} + @Test + public void inject() { + // Check that we can instantiate it + app.injector().instanceOf(javaguide.cache.inject.Application.class); + // Check that we can instantiate the qualified one + app.injector().instanceOf(javaguide.cache.qualified.Application.class); + } - @Test - public void inject() { - // Check that we can instantiate it - app.injector().instanceOf(javaguide.cache.inject.Application.class); - // Check that we can instantiate the qualified one - app.injector().instanceOf(javaguide.cache.qualified.Application.class); + @Test + public void simple() { + AsyncCacheApi cache = app.injector().instanceOf(AsyncCacheApi.class); + + News frontPageNews = new News(); + { + // #simple-set + CompletionStage result = cache.set("item.key", frontPageNews); + // #simple-set + block(result); + // #time-set + } + { + // Cache for 15 minutes + CompletionStage result = cache.set("item.key", frontPageNews, 60 * 15); + // #time-set + block(result); } + // #get + CompletionStage news = cache.get("item.key"); + // #get + assertThat(block(news), equalTo(frontPageNews)); + // #get-or-else + CompletionStage maybeCached = + cache.getOrElseUpdate("item.key", this::lookUpFrontPageNews); + // #get-or-else + assertThat(block(maybeCached), equalTo(frontPageNews)); + { + // #remove + CompletionStage result = cache.remove("item.key"); + // #remove - @Test - public void simple() { - CacheApi cache = app.injector().instanceOf(CacheApi.class); - - News frontPageNews = new News(); - //#simple-set - cache.set("item.key", frontPageNews); - //#simple-set - //#time-set - // Cache for 15 minutes - cache.set("item.key", frontPageNews, 60 * 15); - //#time-set - //#get - News news = cache.get("item.key"); - //#get - assertThat(news, equalTo(frontPageNews)); - //#get-or-else - News maybeCached = cache.getOrElse("item.key", () -> lookUpFrontPageNews()); - //#get-or-else - //#remove - cache.remove("item.key"); - //#remove - assertThat(cache.get("item.key"), nullValue()); + // #removeAll + CompletionStage resultAll = cache.removeAll(); + // #removeAll + block(result); } + assertThat(cache.sync().get("item.key"), nullValue()); + } - private News lookUpFrontPageNews() { - return new News(); + private CompletionStage lookUpFrontPageNews() { + return CompletableFuture.completedFuture(new News()); + } + + public static class Controller1 extends MockJavaAction { + + Controller1(JavaHandlerComponents javaHandlerComponents) { + super(javaHandlerComponents); } - public static class Controller1 extends MockJavaAction { - //#http - @Cached(key = "homePage") - public Result index() { - return ok("Hello world"); - } - //#http + // #http + @Cached(key = "homePage") + public Result index() { + return ok("Hello world"); } + // #http + } + + @Test + public void http() { + AsyncCacheApi cache = app.injector().instanceOf(AsyncCacheApi.class); + + assertThat( + contentAsString( + call(new Controller1(instanceOf(JavaHandlerComponents.class)), fakeRequest(), mat)), + equalTo("Hello world")); + assertThat(cache.sync().get("homePage"), notNullValue()); - @Test - public void http() { - CacheApi cache = app.injector().instanceOf(CacheApi.class); + // Use sync API to ensure the cache is populated before executing the request. + cache.sync().set("homePage", Results.ok("something else")); + assertThat( + contentAsString( + call(new Controller1(instanceOf(JavaHandlerComponents.class)), fakeRequest(), mat)), + equalTo("something else")); + } - assertThat(contentAsString(MockJavaActionHelper.call(new Controller1(), fakeRequest())), equalTo("Hello world")); - assertThat(cache.get("homePage"), notNullValue()); - cache.set("homePage", Results.ok("something else")); - assertThat(contentAsString(MockJavaActionHelper.call(new Controller1(), fakeRequest())), equalTo("something else")); + private static T block(CompletionStage stage) { + try { + return stage.toCompletableFuture().get(); + } catch (Throwable e) { + throw new RuntimeException(e); } + } } diff --git a/manual/working/javaGuide/main/cache/code/javaguide/cache/inject/Application.java b/manual/working/javaGuide/main/cache/code/javaguide/cache/inject/Application.java index abf8805f..029033d8 100644 --- a/manual/working/javaGuide/main/cache/code/javaguide/cache/inject/Application.java +++ b/manual/working/javaGuide/main/cache/code/javaguide/cache/inject/Application.java @@ -1,8 +1,8 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.cache.inject; -//#inject +// #inject import play.cache.*; import play.mvc.*; @@ -10,8 +10,13 @@ public class Application extends Controller { - @Inject CacheApi cache; + private AsyncCacheApi cache; - // ... + @Inject + public Application(AsyncCacheApi cache) { + this.cache = cache; + } + + // ... } -//#inject +// #inject diff --git a/manual/working/javaGuide/main/cache/code/javaguide/cache/qualified/Application.java b/manual/working/javaGuide/main/cache/code/javaguide/cache/qualified/Application.java index a9383207..792a62d9 100644 --- a/manual/working/javaGuide/main/cache/code/javaguide/cache/qualified/Application.java +++ b/manual/working/javaGuide/main/cache/code/javaguide/cache/qualified/Application.java @@ -1,9 +1,9 @@ /* - * Copyright (C) 2009-2015 Typesafe Inc. + * Copyright (C) 2009-2019 Lightbend Inc. */ package javaguide.cache.qualified; -//#qualified +// #qualified import play.cache.*; import play.mvc.*; @@ -11,8 +11,10 @@ public class Application extends Controller { - @Inject @NamedCache("session-cache") CacheApi cache; + @Inject + @NamedCache("session-cache") + SyncCacheApi cache; - // ... + // ... } -//#qualified +// #qualified diff --git a/manual/working/javaGuide/main/cache/index.toc b/manual/working/javaGuide/main/cache/index.toc index 5efee45b..be73b84f 100644 --- a/manual/working/javaGuide/main/cache/index.toc +++ b/manual/working/javaGuide/main/cache/index.toc @@ -1 +1 @@ -JavaCache:キャッシュを使う \ No newline at end of file +JavaCache:Using the Cache \ No newline at end of file diff --git a/manual/working/javaGuide/main/config/JavaConfig.md b/manual/working/javaGuide/main/config/JavaConfig.md new file mode 100644 index 00000000..f4b47faa --- /dev/null +++ b/manual/working/javaGuide/main/config/JavaConfig.md @@ -0,0 +1,14 @@ + +# The Typesafe Config API + +Play uses the [Typesafe config library](https://github.com/typesafehub/config) as the configuration library. If you're not familiar with Typesafe config, you may also want to read the documentation on [[configuration file syntax and features|ConfigFile]]. + +## Accessing the configuration + +Typically, you'll obtain a `Config` object through [[Dependency Injection|JavaDependencyInjection]], or simply by passing an instance of `Config` to your component: + +@[](code/javaguide/config/MyController.java) + +## API documentation + +Since Play just uses `Config` object, you can [see the javadoc for the class](https://static.javadoc.io/com.typesafe/config/1.3.1/com/typesafe/config/Config.html) to see what you can do and how to access configuration data. \ No newline at end of file diff --git a/manual/working/javaGuide/main/config/code/javaguide/config/MyController.java b/manual/working/javaGuide/main/config/code/javaguide/config/MyController.java new file mode 100644 index 00000000..d1c13ff4 --- /dev/null +++ b/manual/working/javaGuide/main/config/code/javaguide/config/MyController.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +// ###replace: package controllers +package javaguide.config; + +import com.typesafe.config.Config; +import play.mvc.Controller; + +import javax.inject.Inject; + +public class MyController extends Controller { + + private final Config config; + + @Inject + public MyController(Config config) { + this.config = config; + } +} diff --git a/manual/working/javaGuide/main/config/index.toc b/manual/working/javaGuide/main/config/index.toc new file mode 100644 index 00000000..26e6c196 --- /dev/null +++ b/manual/working/javaGuide/main/config/index.toc @@ -0,0 +1,2 @@ +JavaConfig:The Config API + diff --git a/manual/working/javaGuide/main/dependencyinjection/JavaCompileTimeDependencyInjection.md b/manual/working/javaGuide/main/dependencyinjection/JavaCompileTimeDependencyInjection.md new file mode 100644 index 00000000..f78ff34f --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/JavaCompileTimeDependencyInjection.md @@ -0,0 +1,111 @@ + +# Compile Time Dependency Injection + +Out of the box, Play provides a mechanism for runtime dependency injection - that is, dependency injection where dependencies aren't wired until runtime. This approach has both advantages and disadvantages, the main advantages being around minimization of boilerplate code, the main disadvantage being that the construction of the application is not validated at compile time. + +An alternative approach is to use compile time dependency injection. At its simplest, compile time DI can be achieved by manually constructing and wiring dependencies. Other more advanced techniques and tools exist, such as [Dagger](https://google.github.io/dagger/). All of these can be easily implemented on top of constructors and manual wiring, so Play's support for compile time dependency injection is provided by providing public constructors and factory methods as API. + +> **Note**: If you're new to compile-time DI or DI in general, it's worth reading Adam Warski's [guide to DI in Scala](https://di-in-scala.github.io/) that discusses compile-time DI in general. While this is an explanation for Scala developers, it could also give you some insights about the advantages of Compile Time Injection. + +In addition to providing public constructors and factory methods, all of Play's out of the box modules provide some interface that implement a lightweight form of the cake pattern, for convenience. These are built on top of the public constructors, and are completely optional. In some applications, they will not be appropriate to use, but in many applications, they will be a very convenient mechanism to wiring the components provided by Play. These interfaces follow a naming convention of ending the trait name with `Components`, so for example, the default HikariCP based implementation of the DB API provides a interface called [HikariCPComponents](api/java/play/db/HikariCPComponents.html). + +> **Note**: Of course, Java has some limitations to fully implement the cake pattern. For example, you can't have state in interfaces. + +In the examples below, we will show how to wire a Play application manually using the built-in component helper interfaces. By reading the source code of the provided component interfaces it should be trivial to adapt this to other dependency injection techniques as well. + +## Application entry point + +Every application that runs on the JVM needs an entry point that is loaded by reflection - even if your application starts itself, the main class is still loaded by reflection, and its main method is located and invoked using reflection. + +In Play's dev mode, the JVM and HTTP server used by Play must be kept running between restarts of your application. To implement this, Play provides an [ApplicationLoader](api/java/play/ApplicationLoader.html) interface that you can implement. The application loader is constructed and invoked every time the application is reloaded, to load the application. + +This interfaces's load method takes as an argument the application loader [Context](api/java/play/ApplicationLoader.Context.html), which contains all the components required by a Play application that outlive the application itself and cannot be constructed by the application itself. A number of these components exist specifically for the purposes of providing functionality in dev mode, for example, the source mapper allows the Play error handlers to render the source code of the place that an exception was thrown. + +The simplest implementation of this can be provided by extending the Play [BuiltInComponentsFromContext](api/java/play/BuiltInComponentsFromContext.html) abstract class. This class takes the context, and provides all the built in components, based on that context. The only thing you need to provide is a router for Play to route requests to. Below is the simplest application that can be created in this way, using an empty router: + +@[basic-imports](code/javaguide/di/components/CompileTimeDependencyInjection.java) + +@[basic-my-components](code/javaguide/di/components/CompileTimeDependencyInjection.java) + +And then the application loader: + +@[basic-app-loader](code/javaguide/di/components/CompileTimeDependencyInjection.java) + +To configure Play to use this application loader, configure the `play.application.loader` property to point to the fully qualified class name in the `application.conf` file: + + play.application.loader=MyApplicationLoader + +In addition, if you're modifying an existing project that uses the built-in Guice module, you should be able to remove `guice` from your `libraryDependencies` in `build.sbt`. + +## Providing a Router + +To configure a router, you have two options, use [`RoutingDsl`](api/java/play/routing/RoutingDsl.html) or the generated router. + +### Providing a router with `RoutingDsl` + +To make this easier, Play has a [`RoutingDslComponentsFromContext`](api/java/play/routing/RoutingDslComponentsFromContext.html) class that already provides a `RoutingDsl` instance, created using the other provided components: + +@[with-routing-dsl](code/javaguide/di/components/CompileTimeDependencyInjection.java) + +### Using the generated router + +By default Play will use the [[injected routes generator|JavaDependencyInjection#Injected-routes-generator]]. This generates a router with a constructor that accepts each of the controllers and included routers from your routes file, in the order they appear in your routes file. The router's constructor will also, as its first argument, accept an [`play.api.http.HttpErrorHandler`](api/scala/play/api/http/HttpErrorHandler.html) (the Scala version of [`play.http.HttpErrorHandler`](api/java/play/http/HttpErrorHandler.html)), which is used to handle parameter binding errors, and a prefix String as its last argument. An overloaded constructor that defaults this to `"/"` will also be provided. + +The following routes: + +@[content](code/javaguide.dependencyinjection.routes) + +Will produce a router that accepts instances of `controllers.HomeController`, `controllers.Assets` and any other that has a declared route. To use this router in an actual application: + +@[with-generated-router](code/javaguide/di/components/CompileTimeDependencyInjection.java) + +## Configuring Logging + +To correctly configure logging in Play, the `LoggerConfigurator` must be run before the application is returned. The default [BuiltInComponentsFromContext](api/java/play/BuiltInComponentsFromContext.html) does not call `LoggerConfigurator` for you. + +This initialization code must be added in your application loader: + +@[basic-logger-configurator](code/javaguide/di/components/CompileTimeDependencyInjection.java) + +## Using other components + +As described before, Play provides a number of helper traits for wiring in other components. For example, if you wanted to use a database connection pool, you can mix in [HikariCPComponents](api/java/play/db/HikariCPComponents.html) into your components cake, like so: + +@[connection-pool](code/javaguide/di/components/CompileTimeDependencyInjection.java) + +Other helper traits are also available as the [CSRFComponents](api/java/play/filters/components/CSRFComponents.html) or the [AhcWSComponents](api/java/play/libs/ws/ahc/AhcWSComponents.html). The complete list of Java interfaces that provides components is: + +- [`play.BuiltInComponents`](api/java/play/BuiltInComponents.html) +- [`play.components.AkkaComponents`](api/java/play/components/AkkaComponents.html) +- [`play.components.ApplicationComponents`](api/java/play/components/ApplicationComponents.html) +- [`play.components.BaseComponents`](api/java/play/components/BaseComponents.html) +- [`play.components.BodyParserComponents`](api/java/play/components/BodyParserComponents.html) +- [`play.components.ConfigurationComponents`](api/java/play/components/ConfigurationComponents.html) +- [`play.components.CryptoComponents`](api/java/play/components/CryptoComponents.html) +- [`play.components.FileMimeTypesComponents`](api/java/play/components/FileMimeTypesComponents.html) +- [`play.components.HttpComponents`](api/java/play/components/HttpComponents.html) +- [`play.components.HttpConfigurationComponents`](api/java/play/components/HttpConfigurationComponents.html) +- [`play.components.HttpErrorHandlerComponents`](api/java/play/components/HttpErrorHandlerComponents.html) +- [`play.components.TemporaryFileComponents`](api/java/play/components/TemporaryFileComponents.html) +- [`play.controllers.AssetsComponents`](api/java/play/controllers/AssetsComponents.html) +- [`play.i18n.I18nComponents`](api/java/play/i18n/I18nComponents.html) +- [`play.libs.ws.ahc.AhcWSComponents`](api/java/play/libs/ws/ahc/AhcWSComponents.html) +- [`play.libs.ws.ahc.WSClientComponents`](api/java/play/libs/ws/ahc/WSClientComponents.html) +- [`play.cache.ehcache.EhCacheComponents`](api/java/play/cache/ehcache/EhCacheComponents.html) +- [`play.filters.components.AllowedHostsComponents`](api/java/play/filters/components/AllowedHostsComponents.html) +- [`play.filters.components.CORSComponents`](api/java/play/filters/components/CORSComponents.html) +- [`play.filters.components.CSRFComponents`](api/java/play/filters/components/CSRFComponents.html) +- [`play.filters.components.GzipFilterComponents`](api/java/play/filters/components/GzipFilterComponents.html) +- [`play.filters.components.HttpFiltersComponents`](api/java/play/filters/components/HttpFiltersComponents.html) +- [`play.filters.components.NoHttpFiltersComponents`](api/java/play/filters/components/NoHttpFiltersComponents.html) +- [`play.filters.components.RedirectHttpsComponents`](api/java/play/filters/components/RedirectHttpsComponents.html) +- [`play.filters.components.SecurityHeadersComponents`](api/java/play/filters/components/SecurityHeadersComponents.html) +- [`play.routing.RoutingDslComponents`](api/java/play/routing/RoutingDslComponents.html) +- [`play.data.FormFactoryComponents`](api/java/play/data/FormFactoryComponents.html) +- [`play.data.validation.ValidatorsComponents`](api/java/play/data/validation/ValidatorsComponents.html) +- [`play.db.BoneCPComponents`](api/java/play/db/BoneCPComponents.html) +- [`play.db.ConnectionPoolComponents`](api/java/play/db/ConnectionPoolComponents.html) +- [`play.db.DBComponents`](api/java/play/db/DBComponents.html) +- [`play.db.HikariCPComponents`](api/java/play/db/HikariCPComponents.html) +- [`play.db.jpa.JPAComponents`](api/java/play/db/jpa/JPAComponents.html) +- [`play.libs.openid.OpenIdComponents`](api/java/play/libs/openid/OpenIdComponents.html) \ No newline at end of file diff --git a/manual/working/javaGuide/advanced/dependencyinjection/JavaDependencyInjection.md b/manual/working/javaGuide/main/dependencyinjection/JavaDependencyInjection.md similarity index 50% rename from manual/working/javaGuide/advanced/dependencyinjection/JavaDependencyInjection.md rename to manual/working/javaGuide/main/dependencyinjection/JavaDependencyInjection.md index 30c90dfc..4fbfdfec 100644 --- a/manual/working/javaGuide/advanced/dependencyinjection/JavaDependencyInjection.md +++ b/manual/working/javaGuide/main/dependencyinjection/JavaDependencyInjection.md @@ -1,21 +1,51 @@ - + # Dependency Injection -Dependency injection is a way that you can separate your components so that they are not directly dependent on each other, rather, they get injected into each other. +Dependency injection is a widely used design pattern that helps to separate your components' behaviour from dependency resolution. Components declare their dependencies, usually as constructor parameters, and a dependency injection framework helps you wire together those components so you don't have to do so manually. -Out of the box, Play provides dependency injection support based on [JSR 330](https://jcp.org/en/jsr/detail?id=330). The default JSR 330 implementation that comes with Play is [Guice](https://github.com/google/guice), but other JSR 330 implementations can be plugged in. +Out of the box, Play provides dependency injection support based on [JSR 330](https://jcp.org/en/jsr/detail?id=330). The default JSR 330 implementation that comes with Play is [Guice](https://github.com/google/guice), but other JSR 330 implementations can be plugged in. To enable the Play-provided Guice module, make sure you have `guice` in your library dependencies in build.sbt, e.g.: + +```scala +libraryDependencies += guice +``` + +The [Guice wiki](https://github.com/google/guice/wiki/) is a great resource for learning more about the features of Guice and DI design patterns in general. + +## Motivation + +Dependency injection achieves several goals: + 1. It allows you to easily bind different implementations for the same component. This is useful especially for testing, where you can manually instantiate components using mock dependencies or inject an alternate implementation. + 2. It allows you to avoid global static state. While static factories can achieve the first goal, you have to be careful to make sure your state is set up properly. In particular Play's (now deprecated) static APIs require a running application, which makes testing less flexible. And having more than one instance available at a time makes it possible to run tests in parallel. + +The [Guice wiki](https://github.com/google/guice/wiki/Motivation) has some good examples explaining this in more detail. + +## How it works + +Play provides a number of built-in components and declares them in modules such as its [BuiltinModule](api/scala/play/api/inject/BuiltinModule.html). These bindings describe everything that's needed to create an instance of `Application`, including, by default, a router generated by the routes compiler that has your controllers injected into the constructor. These bindings can then be translated to work in Guice and other runtime DI frameworks. + +The Play team maintains the Guice module, which provides a [GuiceApplicationLoader](api/scala/play/api/inject/guice/GuiceApplicationLoader.html). That does the binding conversion for Guice, creates the Guice injector with those bindings, and requests an `Application` instance from the injector. + +There are also third-party loaders that do this for other frameworks, including [Spring](https://github.com/remithieblin/play-spring-loader). + +We explain how to customize the default bindings and application loader in more detail below. ## Declaring dependencies -If you have a component, such as a controller, and it requires some other components as dependencies, then this can be declared using the [@Inject](https://docs.oracle.com/javaee/7/api/javax/inject/Inject.html) annotation. The `@Inject` annotation can be used on fields or on constructors, which you decide to use is up to you. For example, to use field injection: +If you have a component, such as a controller, and it requires some other components as dependencies, then this can be declared using the [@Inject](https://docs.oracle.com/javaee/7/api/javax/inject/Inject.html) annotation. The `@Inject` annotation can be used on fields or on constructors. For example, to use field injection: -@[field](code/javaguide/advanced/di/field/MyComponent.java) +@[field](code/javaguide/di/field/MyComponent.java) + +Note that those are *instance* fields. It generally doesn't make sense to inject a static field, since it would break encapsulation. To use constructor injection: -@[constructor](code/javaguide/advanced/di/constructor/MyComponent.java) +@[constructor](code/javaguide/di/constructor/MyComponent.java) + +Field injection is shorter, but we recommend using constructor injection in your application. It is the most testable, since in a unit test you need to pass all the constructor arguments to create an instance of your class, and the compiler makes sure the dependencies are all there. It is also easy to understand what is going on, since there is no "magic" setting of fields going on. The DI framework is just automating the same constructor call you could write manually. -Each of these have their own benefits, and which is best is a matter of hot debate. For brevity, in the Play documentation, we use field injection, but in Play itself, we use constructor injection. +Guice also has several other [types of injections](https://github.com/google/guice/wiki/Injections) which may be useful in some cases. If you are migrating an application that uses statics, you may find its static injection support useful. + +Guice is able to automatically instantiate any class with an `@Inject` on its constructor without having to explicitly bind it. This feature is called [just in time bindings](https://github.com/google/guice/wiki/JustInTimeBindings) is described in more detail in the Guice documentation. If you need to do something more sophisticated you can declare a custom binding as described below. ## Dependency injecting controllers @@ -23,21 +53,25 @@ There are two ways to make Play use dependency injected controllers. ### Injected routes generator -By default, Play will generate a static router, that assumes that all actions are static methods. By configuring Play to use the injected routes generator, you can get Play to generate a router that will declare all the controllers that it routes to as dependencies, allowing your controllers to be dependency injected themselves. - -We recommend always using the injected routes generator, the static routes generator exists primarily as a tool to aid migration so that existing projects don't have to make all their controllers non static at once. +By default (since 2.5.0), Play generates a router class that declares your controllers as dependencies in the constructor. This allows your controllers to be injected into the router. -To enable the injected routes generator, add the following to your build settings in `build.sbt`: +To enable the injected routes generator specifically, add the following to your build settings in `build.sbt`: @[content](code/injected.sbt) When using the injected routes generator, prefixing the action with an `@` symbol takes on a special meaning, it means instead of the controller being injected directly, a `Provider` of the controller will be injected. This allows, for example, prototype controllers, as well as an option for breaking cyclic dependencies. -### Injected actions +### Static routes generator + +You can configure Play to use the legacy (pre 2.5.0) static routes generator, that assumes that all actions are static methods. To configure the project, add the following to build.sbt: + +@[content](code/static.sbt) + +We recommend always using the injected routes generator. The static routes generator exists primarily as a tool to aid migration so that existing projects don't have to make all their controllers non static at once. If using the static routes generator, you can indicate that an action has an injected controller by prefixing the action with `@`, like so: -@[content](code/javaguide.advanced.di.routes) +@[content](code/javaguide.di.routes) ## Component lifecycle @@ -51,13 +85,13 @@ The dependency injection system manages the lifecycle of injected components, cr Sometimes you may have a component that holds some state, such as a cache, or a connection to an external resource, or a component might be expensive to create. In these cases it may be important that there is only one instance of that component. This can be achieved using the [@Singleton](https://docs.oracle.com/javaee/7/api/javax/inject/Singleton.html) annotation: -@[singleton](code/javaguide/advanced/di/CurrentSharePrice.java) +@[singleton](code/javaguide/di/CurrentSharePrice.java) ## Stopping/cleaning up Some components may need to be cleaned up when Play shuts down, for example, to stop thread pools. Play provides an [ApplicationLifecycle](api/java/play/inject/ApplicationLifecycle.html) component that can be used to register hooks to stop your component when Play shuts down: -@[cleanup](code/javaguide/advanced/di/MessageQueueConnection.java) +@[cleanup](code/javaguide/di/MessageQueueConnection.java) The `ApplicationLifecycle` will stop all components in reverse order from when they were created. This means any components that you depend on can still safely be used in your components stop hook, since because you depend on them, they must have been created before your component was, and therefore won't be stopped until after your component is stopped. @@ -80,34 +114,44 @@ Since Play provides support for Guice out of the box, the examples below show ho The simplest way to bind an implementation to an interface is to use the Guice [@ImplementedBy](https://google.github.io/guice/api-docs/latest/javadoc/index.html?com/google/inject/ImplementedBy.html) annotation. For example: -@[implemented-by](code/javaguide/advanced/di/Hello.java) -@[implemented-by](code/javaguide/advanced/di/EnglishHello.java) +@[implemented-by](code/javaguide/di/Hello.java) +@[implemented-by](code/javaguide/di/EnglishHello.java) #### Programmatic bindings -In some more complex situations, you may want to provide more complex bindings, such as when you have multiple implementations of the one trait, which are qualified by [@Named](https://docs.oracle.com/javaee/7/api/javax/inject/Named.html) annotations. In these cases, you can implement a custom Guice [Module](https://google.github.io/guice/api-docs/latest/javadoc/index.html?com/google/inject/Module.html): +In some more complex situations, you may want to provide more complex bindings, such as when you have multiple implementations of the one interface, which are qualified by [@Named](https://docs.oracle.com/javaee/7/api/javax/inject/Named.html) annotations. In these cases, you can implement a custom Guice [Module](https://google.github.io/guice/api-docs/latest/javadoc/index.html?com/google/inject/Module.html): -@[guice-module](code/javaguide/advanced/di/guice/HelloModule.java) +@[guice-module](code/javaguide/di/guice/Module.java) -To register this module with Play, append it's fully qualified class name to the `play.modules.enabled` list in `application.conf`: +If you call this module `Module` and place it in the root package, it will automatically be registered with Play. Alternatively, if you want to give it a different name or put it in a different package, you can register it with Play by appending its fully qualified class name to the `play.modules.enabled` list in `application.conf`: play.modules.enabled += "modules.HelloModule" +You can also disable the automatic registration of a module named `Module` in the root package by adding it to the disabled modules: + + play.modules.disabled += "Module" + #### Configurable bindings -Sometimes you might want to read the Play `Configuration` or use a `ClassLoader` when you configure Guice bindings. You can get access to these objects by adding them to your module's constructor. +Sometimes you might want to read the [`Config`](https://lightbend.github.io/config/latest/api/com/typesafe/config/Config.html) or use a `ClassLoader` when you configure Guice bindings. You can get access to these objects by adding them to your module's constructor. In the example below, the `Hello` binding for each language is read from a configuration file. This allows new `Hello` bindings to be added by adding new settings in your `application.conf` file. -@[dynamic-guice-module](code/javaguide/advanced/di/guice/dynamic/HelloModule.java) +@[dynamic-guice-module](code/javaguide/di/guice/dynamic/Module.java) -> **Note:** In most cases, if you need to access `Configuration` when you create a component, you should inject the `Configuration` object into the component itself or into the component's `Provider`. Then you can read the `Configuration` when you create the component. You usually don't need to read `Configuration` when you create the bindings for the component. +> **Note:** In most cases, if you need to access `Config` when you create a component, you should inject the `Config` object into the component itself or into the component's `Provider`. Then you can read the `Config` when you create the component. You usually don't need to read `Config` when you create the bindings for the component. #### Eager bindings -In the code above, new `EnglishHello` and `GermanHello` objects will be created each time they are used. If you only want to create these objects once, perhaps because they're expensive to create, then you should use the `@Singleton` annotation as [described above](#Singletons). If you want to create them once and also create them _eagerly_ when the application starts up, rather than lazily when they are needed, then you can use [Guice's eager singleton binding](https://github.com/google/guice/wiki/Scopes#eager-singletons). +In the code above, new `EnglishHello` and `GermanHello` objects will be created each time they are used. If you only want to create these objects once, perhaps because they're expensive to create, then you should use the [`@Singleton`](#Singletons) annotation. If you want to create them once and also create them _eagerly_ when the application starts up, rather than lazily when they are needed, then you can use [Guice's eager singleton binding](https://github.com/google/guice/wiki/Scopes#eager-singletons). + +@[eager-guice-module](code/javaguide/di/guice/eager/Module.java) + +Eager singletons can be used to start up a service when an application starts. They are often combined with a [shutdown hook](#Stopping/cleaning-up) so that the service can clean up its resources when the application stops. + +@[eager-guice-module](code/javaguide/di/guice/eager/ApplicationStart.java) -@[eager-guice-module](code/javaguide/advanced/di/guice/eager/HelloModule.java) +@[eager-guice-module](code/javaguide/di/guice/eager/StartModule.java) ### Play libraries @@ -115,7 +159,7 @@ If you're implementing a library for Play, then you probably want it to be DI fr To provide bindings, implement a [Module](api/scala/play/api/inject/Module.html) to return a sequence of the bindings that you want to provide. The `Module` trait also provides a DSL for building bindings: -@[play-module](code/javaguide/advanced/di/playlib/HelloModule.java) +@[play-module](code/javaguide/di/playlib/HelloModule.java) This module can be registered with Play automatically by appending it to the `play.modules.enabled` list in `reference.conf`: @@ -124,7 +168,7 @@ This module can be registered with Play automatically by appending it to the `pl * The `Module` `bindings` method takes a Play `Environment` and `Configuration`. You can access these if you want to [configure the bindings dynamically](#Configurable-bindings). * Module bindings support [eager bindings](#Eager-bindings). To declare an eager binding, add `.eagerly()` at the end of your `Binding`. -In order to maximise cross framework compatibility, keep in mind the following things: +In order to maximize cross framework compatibility, keep in mind the following things: * Not all DI frameworks support just in time bindings. Make sure all components that your library provides are explicitly bound. * Try to keep binding keys simple - different runtime DI frameworks have very different views on what a key is and how it should be unique or not. @@ -135,6 +179,20 @@ If there is a module that you don't want to be loaded, you can exclude it by app play.modules.disabled += "play.api.db.evolutions.EvolutionsModule" +## Managing circular dependencies + +Circular dependencies happen when one of your components depends on another component that depends on the original component (either directly or indirectly). For example: + +@[circular](code/javaguide/di/guice/CircularDependencies.java) + +In this case, `Foo` depends on `Bar`, which depends on `Baz`, which depends on `Foo`. So you won't be able to instantiate any of these classes. You can work around this problem by using a `Provider`: + +@[circular-provider](code/javaguide/di/guice/CircularDependencies.java) + +Note that if you're using constructor injection it will be much more clear when you have a circular dependency, since it will be impossible to instantiate the component manually. + +Generally, circular dependencies can be resolved by breaking up your components in a more atomic way, or finding a more specific component to depend on. A common problem is a dependency on `Application`. When your component depends on `Application` it's saying that it needs a complete application to do its job; typically that's not the case. Your dependencies should be on more specific components (e.g. `Environment`) that have the specific functionality you need. As a last resort you can work around the problem by injecting a `Provider`. + ## Advanced: Extending the GuiceApplicationLoader Play's runtime dependency injection is bootstrapped by the [`GuiceApplicationLoader`](api/java/play/inject/guice/GuiceApplicationLoader.html) class. This class loads all the modules, feeds the modules into Guice, then uses Guice to create the application. If you want to control how Guice initializes the application then you can extend the `GuiceApplicationLoader` class. @@ -142,7 +200,7 @@ Play's runtime dependency injection is bootstrapped by the [`GuiceApplicationLoa There are several methods you can override, but you'll usually want to override the `builder` method. This method reads the [`ApplicationLoader.Context`](api/java/play/ApplicationLoader.Context.html) and creates a [`GuiceApplicationBuilder`](api/java/play/inject/guice/GuiceApplicationBuilder.html ). Below you can see the standard implementation for `builder`, which you can change in any way you like. You can find out how to use the `GuiceApplicationBuilder` in the section about [[testing with Guice|JavaTestingWithGuice]]. -@[custom-application-loader](code/javaguide/advanced/di/guice/CustomApplicationLoader.java) +@[custom-application-loader](code/javaguide/di/guice/CustomApplicationLoader.java) When you override the [`ApplicationLoader`](api/java/play/ApplicationLoader.html) you need to tell Play. Add the following setting to your `application.conf`: diff --git a/manual/working/javaGuide/main/dependencyinjection/code/injected.sbt b/manual/working/javaGuide/main/dependencyinjection/code/injected.sbt new file mode 100644 index 00000000..7c8964a4 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/injected.sbt @@ -0,0 +1,7 @@ +// +// Copyright (C) 2009-2019 Lightbend Inc. +// + +//#content +routesGenerator := InjectedRoutesGenerator +//#content diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide.dependencyinjection.routes b/manual/working/javaGuide/main/dependencyinjection/code/javaguide.dependencyinjection.routes new file mode 100644 index 00000000..3c00e78e --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide.dependencyinjection.routes @@ -0,0 +1,4 @@ +#content +GET / controllers.HomeController.index +GET /assets/*file controllers.Assets.at(path = "/public", file) +#content diff --git a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide.advanced.di.routes b/manual/working/javaGuide/main/dependencyinjection/code/javaguide.di.routes similarity index 88% rename from manual/working/javaGuide/advanced/dependencyinjection/code/javaguide.advanced.di.routes rename to manual/working/javaGuide/main/dependencyinjection/code/javaguide.di.routes index 4c44ba32..4c0120b8 100644 --- a/manual/working/javaGuide/advanced/dependencyinjection/code/javaguide.advanced.di.routes +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide.di.routes @@ -1,3 +1,3 @@ #content GET /some/path @controllers.Application.index() -#content \ No newline at end of file +#content diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/dependencyinjection/controllers/Assets.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/dependencyinjection/controllers/Assets.java new file mode 100644 index 00000000..432690b0 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/dependencyinjection/controllers/Assets.java @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.dependencyinjection.controllers; + +import controllers.AssetsMetadata; +import play.api.http.HttpErrorHandler; + +public class Assets extends controllers.Assets { + public Assets(HttpErrorHandler errorHandler, AssetsMetadata meta) { + super(errorHandler, meta); + } +} diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/dependencyinjection/controllers/HomeController.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/dependencyinjection/controllers/HomeController.java new file mode 100644 index 00000000..2262c36a --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/dependencyinjection/controllers/HomeController.java @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.dependencyinjection.controllers; + +import play.mvc.Controller; +import play.mvc.Result; + +public class HomeController extends Controller { + public Result index() { + return ok(); + } +} diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/CurrentSharePrice.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/CurrentSharePrice.java new file mode 100644 index 00000000..33555666 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/CurrentSharePrice.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di; + +// #singleton +import javax.inject.*; + +@Singleton +public class CurrentSharePrice { + private volatile int price; + + public void set(int p) { + price = p; + } + + public int get() { + return price; + } +} +// #singleton diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/EnglishHello.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/EnglishHello.java new file mode 100644 index 00000000..33a0e480 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/EnglishHello.java @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di; + +// #implemented-by +public class EnglishHello implements Hello { + + public String sayHello(String name) { + return "Hello " + name; + } +} +// #implemented-by diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/GermanHello.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/GermanHello.java new file mode 100644 index 00000000..8b8e9bfe --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/GermanHello.java @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di; + +public class GermanHello implements Hello { + @Override + public String sayHello(String name) { + return "Hallo " + name; + } +} diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/Hello.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/Hello.java new file mode 100644 index 00000000..43d1ca6a --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/Hello.java @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di; + +// #implemented-by +import com.google.inject.ImplementedBy; + +@ImplementedBy(EnglishHello.class) +public interface Hello { + + String sayHello(String name); +} +// #implemented-by diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/JavaDependencyInjection.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/JavaDependencyInjection.java new file mode 100644 index 00000000..ac32425f --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/JavaDependencyInjection.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di; + +import play.test.*; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; + +public class JavaDependencyInjection extends WithApplication { + + @Test + public void fieldInjection() { + assertNotNull(app.injector().instanceOf(javaguide.di.field.MyComponent.class)); + } + + @Test + public void constructorInjection() { + assertNotNull(app.injector().instanceOf(javaguide.di.constructor.MyComponent.class)); + } + + @Test + public void singleton() { + app.injector().instanceOf(CurrentSharePrice.class).set(10); + assertThat(app.injector().instanceOf(CurrentSharePrice.class).get(), equalTo(10)); + } + + @Test + public void cleanup() { + app.injector().instanceOf(MessageQueueConnection.class); + stopPlay(); + assertTrue(MessageQueue.stopped); + } + + @Test + public void implementedBy() { + assertThat(app.injector().instanceOf(Hello.class).sayHello("world"), equalTo("Hello world")); + } +} diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/MessageQueue.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/MessageQueue.java new file mode 100644 index 00000000..75a19820 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/MessageQueue.java @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di; + +public class MessageQueue { + public static boolean stopped = false; + + public static MessageQueue connect() { + return new MessageQueue(); + } + + public void stop() { + stopped = true; + } +} diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/MessageQueueConnection.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/MessageQueueConnection.java new file mode 100644 index 00000000..7dd707f2 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/MessageQueueConnection.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di; + +// #cleanup +import javax.inject.*; +import play.inject.ApplicationLifecycle; + +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; + +@Singleton +public class MessageQueueConnection { + private final MessageQueue connection; + + @Inject + public MessageQueueConnection(ApplicationLifecycle lifecycle) { + connection = MessageQueue.connect(); + + lifecycle.addStopHook( + () -> { + connection.stop(); + return CompletableFuture.completedFuture(null); + }); + } + + // ... +} +// #cleanup diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/components/CompileTimeDependencyInjection.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/components/CompileTimeDependencyInjection.java new file mode 100644 index 00000000..e1a4e5a4 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/components/CompileTimeDependencyInjection.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.components; + +// #basic-imports +import play.Application; +import play.ApplicationLoader; +import play.BuiltInComponentsFromContext; +import play.LoggerConfigurator; +import play.controllers.AssetsComponents; +import play.db.ConnectionPool; +import play.db.HikariCPComponents; +import play.filters.components.HttpFiltersComponents; +import play.mvc.Results; +import play.routing.Router; +import play.routing.RoutingDslComponentsFromContext; +// #basic-imports + +import javaguide.dependencyinjection.controllers.Assets; +import javaguide.dependencyinjection.controllers.HomeController; + +public class CompileTimeDependencyInjection { + + // #basic-app-loader + public class MyApplicationLoader implements ApplicationLoader { + + @Override + public Application load(Context context) { + return new MyComponents(context).application(); + } + } + // #basic-app-loader + + // #basic-my-components + public class MyComponents extends BuiltInComponentsFromContext implements HttpFiltersComponents { + + public MyComponents(ApplicationLoader.Context context) { + super(context); + } + + @Override + public Router router() { + return Router.empty(); + } + } + // #basic-my-components + + // #basic-logger-configurator + // ###insert: import scala.compat.java8.OptionConverters; + public class MyAppLoaderWithLoggerConfiguration implements ApplicationLoader { + @Override + public Application load(Context context) { + + LoggerConfigurator.apply(context.environment().classLoader()) + .ifPresent( + loggerConfigurator -> + loggerConfigurator.configure(context.environment(), context.initialConfig())); + + return new MyComponents(context).application(); + } + } + // #basic-logger-configurator + + // #connection-pool + public class MyComponentsWithDatabase extends BuiltInComponentsFromContext + implements HikariCPComponents, HttpFiltersComponents { + + public MyComponentsWithDatabase(ApplicationLoader.Context context) { + super(context); + } + + @Override + public Router router() { + return Router.empty(); + } + + public SomeComponent someComponent() { + // connectionPool method is provided by HikariCPComponents + return new SomeComponent(connectionPool()); + } + } + // #connection-pool + + static class SomeComponent { + SomeComponent(ConnectionPool pool) { + // do nothing + } + } + + // #with-routing-dsl + public class MyComponentsWithRouter extends RoutingDslComponentsFromContext + implements HttpFiltersComponents { + + public MyComponentsWithRouter(ApplicationLoader.Context context) { + super(context); + } + + @Override + public Router router() { + // routingDsl method is provided by RoutingDslComponentsFromContext + return routingDsl().GET("/path").routeTo(() -> Results.ok("The content")).build(); + } + } + // #with-routing-dsl + + // #with-generated-router + public class MyComponentsWithGeneratedRouter extends BuiltInComponentsFromContext + implements HttpFiltersComponents, AssetsComponents { + + public MyComponentsWithGeneratedRouter(ApplicationLoader.Context context) { + super(context); + } + + @Override + public Router router() { + HomeController homeController = new HomeController(); + Assets assets = new Assets(scalaHttpErrorHandler(), assetsMetadata()); + // ###replace: return new router.Routes(scalaHttpErrorHandler(), homeController, + // assets).asJava(); + return new javaguide.dependencyinjection.Routes( + scalaHttpErrorHandler(), homeController, assets) + .asJava(); + } + } + // #with-generated-router +} diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/constructor/MyComponent.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/constructor/MyComponent.java new file mode 100644 index 00000000..1611bf96 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/constructor/MyComponent.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.constructor; + +// #constructor +import javax.inject.*; +import play.libs.ws.*; + +public class MyComponent { + private final WSClient ws; + + @Inject + public MyComponent(WSClient ws) { + this.ws = ws; + } + + // ... +} +// #constructor diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/controllers/Application.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/controllers/Application.java new file mode 100644 index 00000000..8679ed6d --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/controllers/Application.java @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.controllers; + +import play.mvc.*; + +public class Application extends Controller { + public Result index() { + return ok(); + } +} diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/field/MyComponent.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/field/MyComponent.java new file mode 100644 index 00000000..040773e0 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/field/MyComponent.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.field; + +// #field +import javax.inject.*; +import play.libs.ws.*; + +public class MyComponent { + @Inject WSClient ws; + + // ... +} +// #field diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/CircularDependencies.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/CircularDependencies.java new file mode 100644 index 00000000..a8c525fe --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/CircularDependencies.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.guice; + +import javax.inject.Inject; +import javax.inject.Provider; + +class CircularDependencies { + + class NoProvider { + // #circular + public class Foo { + @Inject + public Foo(Bar bar) { + // ... + } + } + + public class Bar { + @Inject + public Bar(Baz baz) { + // ... + } + } + + public class Baz { + @Inject + public Baz(Foo foo) { + // ... + } + } + // #circular + } + + class WithProvider { + // #circular-provider + public class Foo { + @Inject + public Foo(Bar bar) { + // ... + } + } + + public class Bar { + @Inject + public Bar(Baz baz) { + // ... + } + } + + public class Baz { + @Inject + public Baz(Provider fooProvider) { + // ... + } + } + // #circular-provider + } +} diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/CustomApplicationLoader.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/CustomApplicationLoader.java new file mode 100644 index 00000000..5deadd45 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/CustomApplicationLoader.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.guice; + +// #custom-application-loader +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import play.ApplicationLoader; +import play.inject.guice.GuiceApplicationBuilder; +import play.inject.guice.GuiceApplicationLoader; + +public class CustomApplicationLoader extends GuiceApplicationLoader { + + @Override + public GuiceApplicationBuilder builder(ApplicationLoader.Context context) { + Config extra = ConfigFactory.parseString("a = 1"); + return initialBuilder + .in(context.environment()) + .loadConfig(extra.withFallback(context.initialConfig())) + .overrides(overrides(context)); + } +} +// #custom-application-loader diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/Module.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/Module.java new file mode 100644 index 00000000..a01d525c --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/Module.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.guice; + +import javaguide.di.*; + +// #guice-module +import com.google.inject.AbstractModule; +import com.google.inject.name.Names; + +public class Module extends AbstractModule { + protected void configure() { + + bind(Hello.class).annotatedWith(Names.named("en")).to(EnglishHello.class); + + bind(Hello.class).annotatedWith(Names.named("de")).to(GermanHello.class); + } +} +// #guice-module diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/dynamic/Module.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/dynamic/Module.java new file mode 100644 index 00000000..07009f93 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/dynamic/Module.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.guice.configured; + +import javaguide.di.*; + +// #dynamic-guice-module +import com.google.inject.AbstractModule; +import com.google.inject.name.Names; +import com.typesafe.config.Config; +import play.Environment; + +public class Module extends AbstractModule { + + private final Environment environment; + private final Config config; + + public Module(Environment environment, Config config) { + this.environment = environment; + this.config = config; + } + + protected void configure() { + // Expect configuration like: + // hello.en = "myapp.EnglishHello" + // hello.de = "myapp.GermanHello" + final Config helloConf = config.getConfig("hello"); + // Iterate through all the languages and bind the + // class associated with that language. Use Play's + // ClassLoader to load the classes. + helloConf + .entrySet() + .forEach( + entry -> { + try { + String name = entry.getKey(); + Class bindingClass = + environment + .classLoader() + .loadClass(entry.getValue().toString()) + .asSubclass(Hello.class); + bind(Hello.class).annotatedWith(Names.named(name)).to(bindingClass); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + }); + } +} +// #dynamic-guice-module diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/eager/ApplicationStart.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/eager/ApplicationStart.java new file mode 100644 index 00000000..92f51ae2 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/eager/ApplicationStart.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.guice.eager; + +import javaguide.di.*; + +// #eager-guice-module +import javax.inject.*; +import play.inject.ApplicationLifecycle; +import play.Environment; +import java.util.concurrent.CompletableFuture; + +// This creates an `ApplicationStart` object once at start-up. +@Singleton +public class ApplicationStart { + + // Inject the application's Environment upon start-up and register hook(s) for shut-down. + @Inject + public ApplicationStart(ApplicationLifecycle lifecycle, Environment environment) { + // Shut-down hook + lifecycle.addStopHook( + () -> { + return CompletableFuture.completedFuture(null); + }); + // ... + } +} +// #eager-guice-module diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/eager/Module.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/eager/Module.java new file mode 100644 index 00000000..3b2a6087 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/eager/Module.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.guice.eager; + +import javaguide.di.*; + +// #eager-guice-module +import com.google.inject.AbstractModule; +import com.google.inject.name.Names; + +// A Module is needed to register bindings +public class Module extends AbstractModule { + protected void configure() { + + // Bind the `Hello` interface to the `EnglishHello` implementation as eager singleton. + bind(Hello.class).annotatedWith(Names.named("en")).to(EnglishHello.class).asEagerSingleton(); + + bind(Hello.class).annotatedWith(Names.named("de")).to(GermanHello.class).asEagerSingleton(); + } +} +// #eager-guice-module diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/eager/StartModule.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/eager/StartModule.java new file mode 100644 index 00000000..76a2c2b8 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/guice/eager/StartModule.java @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.guice.eager; + +import javaguide.di.*; + +// #eager-guice-module +import com.google.inject.AbstractModule; + +public class StartModule extends AbstractModule { + protected void configure() { + bind(ApplicationStart.class).asEagerSingleton(); + } +} +// #eager-guice-module diff --git a/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/playlib/HelloModule.java b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/playlib/HelloModule.java new file mode 100644 index 00000000..0db2bfed --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/javaguide/di/playlib/HelloModule.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ +package javaguide.di.playlib; + +import javaguide.di.*; + +// #play-module +import play.api.*; +import play.api.inject.*; +import scala.collection.Seq; + +public class HelloModule extends Module { + @Override + public Seq> bindings(Environment environment, Configuration configuration) { + return seq( + bind(Hello.class).qualifiedWith("en").to(EnglishHello.class), + bind(Hello.class).qualifiedWith("de").to(GermanHello.class)); + } +} +// #play-module diff --git a/manual/working/javaGuide/main/dependencyinjection/code/static.sbt b/manual/working/javaGuide/main/dependencyinjection/code/static.sbt new file mode 100644 index 00000000..f7fbf775 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/code/static.sbt @@ -0,0 +1,3 @@ +//#content +routesGenerator := StaticRoutesGenerator +//#content diff --git a/manual/working/javaGuide/main/dependencyinjection/index.toc b/manual/working/javaGuide/main/dependencyinjection/index.toc new file mode 100644 index 00000000..406568b3 --- /dev/null +++ b/manual/working/javaGuide/main/dependencyinjection/index.toc @@ -0,0 +1,2 @@ +JavaDependencyInjection:Dependency Injection with Guice +JavaCompileTimeDependencyInjection:Compile Time Dependency Injection diff --git a/manual/working/javaGuide/main/forms/JavaCsrf.md b/manual/working/javaGuide/main/forms/JavaCsrf.md index 12f9d055..08c8673c 100644 --- a/manual/working/javaGuide/main/forms/JavaCsrf.md +++ b/manual/working/javaGuide/main/forms/JavaCsrf.md @@ -1,57 +1,68 @@ - + # Protecting against Cross Site Request Forgery Cross Site Request Forgery (CSRF) is a security exploit where an attacker tricks a victim's browser into making a request using the victim's session. Since the session token is sent with every request, if an attacker can coerce the victim's browser to make a request on their behalf, the attacker can make requests on the user's behalf. -It is recommended that you familiarise yourself with CSRF, what the attack vectors are, and what the attack vectors are not. We recommend starting with [this information from OWASP](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29). +It is recommended that you familiarize yourself with CSRF, what the attack vectors are, and what the attack vectors are not. We recommend starting with [this information from OWASP](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29). -Simply put, an attacker can coerce a victim's browser to make the following types of requests: +There is no simple answer to what requests are safe and what are vulnerable to CSRF requests, the reason for this is that there is no clear specification as to what is allowable from plugins and future extensions to specifications. Historically, browser plugins and extensions have relaxed the rules that frameworks previously thought could be trusted, introducing CSRF vulnerabilities to many applications, and the onus has been on the frameworks to fix them. For this reason, Play takes a conservative approach in its defaults, but allows you to configure exactly when a check is done. By default, Play will require a CSRF check when all of the following are true: -* All `GET` requests -* `POST` requests with bodies of type `application/x-www-form-urlencoded`, `multipart/form-data` and `text/plain` +* The request method is not `GET`, `HEAD` or `OPTIONS`. +* The request has one or more `Cookie` or `Authorization` headers. +* The CORS filter is not configured to trust the request's origin. -An attacker can not: - -* Coerce the browser to use other request methods such as `PUT` and `DELETE` -* Coerce the browser to post other content types, such as `application/json` -* Coerce the browser to send new cookies, other than those that the server has already set -* Coerce the browser to set arbitrary headers, other than the normal headers the browser adds to requests - -Since `GET` requests are not meant to be mutative, there is no danger to an application that follows this best practice. So the only requests that need CSRF protection are `POST` requests with the above mentioned content types. +> **Note:** If you use browser-based authentication other than using cookies or HTTP authentication, such as NTLM or client certificate based authentication, then you **must** set `play.filters.csrf.header.protectHeaders = null`, or include the headers used in authentication in `protectHeaders`. ### Play's CSRF protection -Play supports multiple methods for verifying that a request is not a CSRF request. The primary mechanism is a CSRF token. This token gets placed either in the query string or body of every form submitted, and also gets placed in the user's session. Play then verifies that both tokens are present and match. +Play supports multiple methods for verifying that a request is not a CSRF request. The primary mechanism is a CSRF token. This token gets placed either in the query string or body of every form submitted, and also gets placed in the users session. Play then verifies that both tokens are present and match. + +To allow simple protection for non browser requests, Play only checks requests with cookies in the header. If you are making requests with AJAX, you can place the CSRF token in the HTML page, and then add it to the request using the `Csrf-Token` header. -To allow simple protection for non browser requests, such as requests made through AJAX, Play also supports the following: +Alternatively, you can set `play.filters.csrf.header.bypassHeaders` to match common headers: A common configuration would be: * If an `X-Requested-With` header is present, Play will consider the request safe. `X-Requested-With` is added to requests by many popular Javascript libraries, such as jQuery. * If a `Csrf-Token` header with value `nocheck` is present, or with a valid CSRF token, Play will consider the request safe. -## Applying a global CSRF filter - -Play provides a global CSRF filter that can be applied to all requests. This is the simplest way to add CSRF protection to an application. To enable the global filter, add the Play filters helpers dependency to your project in `build.sbt`: +This configuration would look like: -```scala -libraryDependencies += filters ``` +play.filters.csrf.header.bypassHeaders { + X-Requested-With = "*" + Csrf-Token = "nocheck" +} +``` + +Caution should be taken when using this configuration option, as historically browser plugins have undermined this type of CSRF defence. + +### Trusting CORS requests -Now add them to your `Filters` class: +By default, if you have a CORS filter before your CSRF filter, the CSRF filter will let through CORS requests from trusted origins. To disable this check, set the config option `play.filters.csrf.bypassCorsTrustedOrigins = false`. + +## Applying a global CSRF filter -@[filters](code/javaguide/forms/csrf/Filters.java) +> **Note:** As of Play 2.6.x, the CSRF filter is included in Play's list of default filters that are applied automatically to projects. See [[the Filters page|Filters]] for more information. -The `Filters` class can either be in the root package, or if it has another name or is in another package, needs to be configured using `play.http.filters` in `application.conf`: +Play provides a global CSRF filter that can be applied to all requests. This is the simplest way to add CSRF protection to an application. To add the filter manually, add it to `application.conf`: ``` -play.http.filters = "filters.MyFilters" +play.filters.enabled += "play.filters.csrf.CSRFFilter" ``` +It is also possible to disable the CSRF filter for a specific route in the routes file. To do this, add the `nocsrf` modifier tag before your route: + +@[nocsrf](../http/code/javaguide.http.routing.routes) + ### Getting the current token -The current CSRF token can be accessed using the `CSRF.getToken` method. It takes a `RequestHeader`, which can be obtained by calling `Controllers.request()`: +The current CSRF token can be accessed using the `CSRF.getToken` method. It takes a [`RequestHeader`](api/java/play/mvc/Http.RequestHeader.html), which can be obtained from [`Http.Context.current()`](api/java/play/mvc/Http.Context.html#current--) with [`context.request()`](api/java/play/mvc/Http.Context.html#request--): @[get-token](code/javaguide/forms/JavaCsrf.java) +> **Note**: If the CSRF filter is installed, Play will try to avoid generating the token as long as the cookie being used is HttpOnly (meaning it cannot be accessed from JavaScript). When sending a response with a strict body, Play skips adding the token to the response unless `CSRF.getToken` has already been called. This results in a significant performance improvement for responses that don't need a CSRF token. If the cookie is not configured to be HttpOnly, Play will assume you wish to access it from JavaScript and generate it regardless. + +> **Note**: if you are accessing the template from a `CompletionStage` and get an `There is no HTTP Context` error, then you will need to add [`HttpExecutionContext.current()`](api/java/play/libs/concurrent/HttpExecutionContext.html) -- see [[JavaAsync]] for details. + To help in adding CSRF tokens to forms, Play provides some template helpers. The first one adds it to the query string of the action URL: @[csrf-call](code/javaguide/forms/csrf.scala.html) @@ -87,11 +98,11 @@ Sometimes global CSRF filtering may not be appropriate, for example in situation In these cases, Play provides two actions that can be composed with your applications actions. -The first action is the `play.filters.csrf.RequireCSRFCheck` action which performs the CSRF check. It should be added to all actions that accept session authenticated POST form submissions: +The first action is the [`play.filters.csrf.RequireCSRFCheck`](api/java/play/filters/csrf/RequireCSRFCheck.html) action which performs the CSRF check. It should be added to all actions that accept session authenticated POST form submissions: @[csrf-check](code/javaguide/forms/JavaCsrf.java) -The second action is the `play.filters.csrf.AddCSRFToken` action, it generates a CSRF token if not already present on the incoming request. It should be added to all actions that render forms: +The second action is the [`play.filters.csrf.AddCSRFToken`](api/java/play/filters/csrf/AddCSRFToken.html) action, it generates a CSRF token if not already present on the incoming request. It should be added to all actions that render forms: @[csrf-add-token](code/javaguide/forms/JavaCsrf.java) @@ -104,3 +115,11 @@ The full range of CSRF configuration options can be found in the filters [refere * `play.filters.csrf.cookie.secure` - If `play.filters.csrf.cookie.name` is set, whether the CSRF cookie should have the secure flag set. Defaults to the same value as `play.http.session.secure`. * `play.filters.csrf.body.bufferSize` - In order to read tokens out of the body, Play must first buffer the body and potentially parse it. This sets the maximum buffer size that will be used to buffer the body. Defaults to 100k. * `play.filters.csrf.token.sign` - Whether Play should use signed CSRF tokens. Signed CSRF tokens ensure that the token value is randomised per request, thus defeating BREACH style attacks. + +## Testing CSRF + + +In a functional test, if you are rendering a Twirl template with a CSRF token, you need to have a CSRF token available. You can do this by calling `play.api.test.CSRFTokenHelper.addCSRFToken` on a `play.mvc.Http.RequestBuilder` instance: + +@[test-with-addCSRFToken](../../../commonGuide/filters/code/javaguide/detailed/filters/FiltersTest.java) + diff --git a/manual/working/javaGuide/main/forms/JavaFormHelpers.md b/manual/working/javaGuide/main/forms/JavaFormHelpers.md index 59f692e8..0214aedf 100644 --- a/manual/working/javaGuide/main/forms/JavaFormHelpers.md +++ b/manual/working/javaGuide/main/forms/JavaFormHelpers.md @@ -1,85 +1,43 @@ - - # Form template helpers ---> -# フォームテンプレートヘルパーの利用 - -Play には HTML テンプレート中にフォームやフィールドを生成するためのヘルパー関数が用意されています。 - -## `
` タグの作成 - - -まず、 `` タグを作成するヘルパーがあります。これは、 `action` と `method` タグパラメータを引数に渡したリバースルートに基づいて設定する、かなりシンプルなヘルパーです。 @[form](code/javaguide/forms/helpers.scala.html) - -生成される HTML に追加されるその他のパラメータを渡すこともできます。 @[form-with-id](code/javaguide/forms/helpers.scala.html) - -## `` 要素のレンダリング - -`views.html.helper` パッケージには input 要素のヘルパーがいくつか用意されています。これらヘルパーにフォームフィールドのオブジェクトを渡すと、それに対応した HTML フォームコントロールが表示されます。コントロールには値がセットされ、制約、エラー情報も付与されます。 @[full-form](code/javaguide/forms/fullform.scala.html) - -`form` ヘルパーと同様に、生成される HTML に追加されるその他のパラメータを指定することができます。 @[extra-params](code/javaguide/forms/helpers.scala.html) - -> **ノート:** `_` という文字以外で始まるパラメータは全て生成される HTML に追加されます。アンダースコアで始まるパラメータは、フィールドコンストラクタの引数(後で説明します)のために予約されています。 - -## input 要素を自由に生成する - -生成される HTML をもっと細かくコントロールしたい時のために、より汎用的な `input` ヘルパーも用意されています。 @[generic-input](code/javaguide/forms/helpers.scala.html) - -## フィールドコンストラクタ - -フィールドをレンダリングする際、単に `` タグを出力するだけでなく、`