This project demonstrate how to create a simple CRUD application with Play.
-
play version: 2.4.2
-
scala version: 2.11.7
-
sbt version: 0.13.9
-
specs2 version: 3.6.3
-
Slick version: 3.0.1
-
bootstrap version: 3.3.5
-
play-mailer version: 3.0.1
- Most Play JARs include a
reference.conf
with default settings application.conf
will overridereference.conf
.
Use for:
- Signing session cookies and CSRF tokens.
- Built in encryption utilities.
- Start Script:
/path/to/yourapp/bin/yourapp -Dplay.crypto.secret="QCY?tAnfk?aZ?iwrNwnxIlR6CTf:G3gf:90Latabg@5241AB`R5W:1uDFN];Ik@n"
- Environment variables:
play.crypto.secret=${?APPLICATION_SECRET}
- Production configuration file:
include "application" play.crypto.secret="QCY?tAnfk?aZ?iwrNwnxIlR6CTf:G3gf:90Latabg@5241AB`R5W:1uDFN];Ik@n"
Run play-generate-secret
in the Play console.
Run play-update-secret
in the Play console
You can not use ==JDBC together with Play-Slick==(throw exception), but Play-Slick can be working with another Play module for database access.
- Add JDBC in
bulid.sbt
:
libraryDependencies += jdbc
-
Database Configuration in
appliction
:- H2:
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
- Add a library dependency in
build.sbt
:
"com.typesafe.play" %% "play-slick" % "1.0.0",
"com.typesafe.play" %% "play-slick-evolutions" % "1.0.0"
- Database Configuration in
appliction
:- H2:
# Default database configuration
slick.dbs.default.driver="slick.driver.H2Driver$"
slick.dbs.default.db.driver="org.h2.Driver"
slick.dbs.default.db.url="jdbc:h2:mem:play"
# Orders database
slick.dbs.orders.driver="slick.driver.H2Driver$"
slick.dbs.orders.db.driver="org.h2.Driver"
slick.dbs.orders.db.url="jdbc:h2:mem:play"
# Customers database
slick.dbs.customers.driver="slick.driver.H2Driver$"
slick.dbs.customers.db.driver="org.h2.Driver"
slick.dbs.customers.db.url="jdbc:h2:mem:play"
See more: Play Slick
-
JDBC Setting in
applicationi.conf
:play.db.pool=bonecp
==(Only for JDBC)==See details:
play-jdbc_2.11-2.4.1.jar/reference.conf
or Play'sreference.conf
-
Slick Play Slick currently only allow using HikariCP.(See more: PlaySlickFAQ)
TODO
- Default: i.e.
akka-actor_2.11-2.3.11.jar!\reference.conf
- Custom: Configuring
application.conf
overridereference.conf
.
akka {
fork-join-executor {
# 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
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 8
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 64
}
}
- Using other thread pools(i.e. Java executors, Scala fork/join): See details: here
Logging(Logback)
-
Default Configuration File:
conf\logback.xml
-
Using an external configuration file:
- Load config file from classpath:
$ start -Dlogger.resource = prod-logger.xml
- Load config file from file system:
$start -Dlogger.file = /opt/prod/logger.xml
- Load config file from classpath:
-
Akka logging:
-
Default: Ignore Play's logging configuration and print log messages to STDOUT. Configure it in
application.conf
:akka { loglevel="INFO" }
-
Direct Akka to use Play's logging engine:
- Config in
application.conf
:
- Config in
-
akka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel="DEBUG"
}
2. Refine your Akka logging setting in Logback config:
<!-- Set logging for all Akka library classes to INFO -->
<logger name="akka" level="INFO" />
<!-- Set a specific actor to DEBUG -->
<logger name="actors.MyActor" level="DEBUG" />
See more info about Akka logging: [Akka documentation](http://doc.akka.io/docs/akka/current/scala/logging.html)
- Configuring filters in
application.conf
:
play.http.requestHandler = "com.example.RequestHandler"
play.http.requestHandler = "play.api.http.DefaultHttpRequestHandler"
- Performance note:
- If you didn't configure Play's HttpRequestHandler, that means use the
GlobalSetting
method. GlobalSetting
have a performance impact - your app has to do many lookups out of Guice to handle a single request.- Instead using
DefaultHttpRequestHandler
avoid the issue.
- If you didn't configure Play's HttpRequestHandler, that means use the
- Add dependency in
build.sbt
:
libraryDependencies += filters
- Configuring filters in
application.conf
:
play.http.filters = "filters.MyFilters"
see details: filters-helpers_2.11-2.4.1.jar!\reference.conf
- Overview the global filter:
package filters
import javax.inject.Inject
import play.api.http.HttpFilters
import play.api.mvc.EssentialFilter
import play.filters.cors.CORSFilter
import play.filters.csrf.CSRFFilter
import play.filters.gzip.GzipFilter
import play.filters.headers.SecurityHeadersFilter
class MyFilters @Inject()(gzipFilter: GzipFilter,
securityHeadersFilter: SecurityHeadersFilter,
cORSFilter: CORSFilter,
cSRFFilter: CSRFFilter) extends HttpFilters {
override def filters: Seq[EssentialFilter] = Seq(gzipFilter, securityHeadersFilter, cORSFilter, cSRFFilter)
}
- Creating gzip
Filters
class:
import javax.inject.Inject
import play.api.http.HttpFilters
import play.filters.gzip.GzipFilter
class Filters @Inject() (gzipFilter: GzipFilter) extends HttpFilters {
def filters = Seq(gzipFilter)
}
- Controlling which responses are gzipped:
new GzipFilter(shouldGzip = (request, response) =>
response.headers.get("Content-Type").exists(_.startsWith("text/html")))
- Creating Security Headers
Filters
class:
import javax.inject.Inject
import play.api.http.HttpFilters
import play.filters.headers.SecurityHeadersFilter
class Filters @Inject() (securityHeadersFilter: SecurityHeadersFilter) extends HttpFilters {
def filters = Seq(securityHeadersFilter)
}
-
Configuring the security headers:
play.filters.headers.frameOptions
- X-Frame-Options: "DENY" by defaultplay.filters.headers.xssProtection
- X-XSS-Protection: "1; mode=block" by defaultplay.filters.headers.contentTypeOptions
- X-Content-Type-Optiond: "nosniff" by defaultplay.filters.headers.permittedCrossDomainPolicies
- X-Permitted-Cross-Domain-Policies: "master-only" by defaultplay.filters.headers.contentSecurityPolicy
- Content-Security-Policy: "default-src" by default
-
Custome the security headers in
application.conf
:# The X-Frame-Options header. If null, the header is not set. play.filters.headers.frameOptions = "SAMEORIGIN" # The X-XSS-Protection header. If null, the header is not set. play.filters.headers.xssProtection = "1; mode=block" # The X-Content-Type-Options header. If null, the header is not set. play.filters.headers.contentTypeOptions = "nosniff" # The X-Permitted-Cross-Domain-Policies header. If null, the header is not set. play.filters.headers.permittedCrossDomainPolicies = "master-only" # The Content-Security-Policy header. If null, the header is not set. play.filters.headers.contentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'"
Cross-Origin Resource Sharing (See more about CORS: here)
- Creating CORS
Filter
class:
import javax.inject.Inject
import play.api.http.HttpFilters
import play.filters.cors.CORSFilter
class Filters @Inject() (corsFilter: CORSFilter) extends HttpFilters {
def filters = Seq(corsFilter)
}
-
Configuring the CORS filter:
play.filters.cors.pathPrefixes
- filter paths by a whitelist of path prefixesplay.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)
Cross-Site Request Forgery(==Sometimes is not appropriate for global filter==)
See more about CSRF is: here
Creating CSRF:Filter
class(Global filter)
import play.api.http.HttpFilters
import play.filters.csrf.CSRFFilter
import javax.inject.Inject
class Filters @Inject() (csrfFilter: CSRFFilter) extends HttpFilters {
def filters = Seq(csrfFilter)
}
- Applying CSRF on a per action(==Recommendation==):
// It generates a CSRF token if not already present on the incoming request.
def save = CSRFAddToken {
Action { req =>
// handle body
Ok
}
}
def form = CSRFCheck {
Action { implicit req =>
Ok(views.html.itemsForm())
}
}
- Using in template:
@(person: Form[CreatePersonForm])(implicit messages: Messages, requestHeader: RequestHeader)
@import helper._
@form(CSRF(routes.PersonController.addPerson())) { ...
// or CSRF.formField
-
CSRF configuration options:
play.filters.csrf.token.name
- The name of the token to use both in the session and in the request body/query string. Defaults to csrfToken.play.filters.csrf.cookie.name
- If configured, Play will store the CSRF token in a cookie with the given name, instead of in the session.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.
Play cache(EHCache)
- Add
cache
inbuild.sbt
:libraryDependencies ++= Seq( cache, ... )
- Configuration in
application.conf
:play.cache.bindCaches = ["db-cache", "user-cache", "session-cache"]
- Example:
```scala
class Application @Inject()(@NamedCache("session-cache") sessionCache: CacheApi) extends Controller {
cache.set("item.key", connectedUser, 5.minutes)
cache.remove("item.key")
val maybeUser: Option[User] = cache.get[User]("item.key", 1.minutes)
val user: User = cache.getOrElse[User]("item.key")(User.findById(connectedUser))
// Cache the index.html page.
def index = Action { implicit request =>
Ok(htmlCache.getOrElse("index-html")(views.html.index(title = "Index", loginForm = loginForm)))
}
- Add
ws
inbuild.sbt
:libraryDependencies ++= Seq( ws, ... )
- Configuration in `application.conf`:
```shell
play.ws.followRedirects = true
play.ws.useProxyProperties = true
play.ws.useragent = null
play.ws.compressionEnabled = false
play.ws.timeout.connection = 2 minutes
play.ws.timeout.idle = 2 minutes
play.ws.timeout.request = 2 minutes
Enable HTTPS(SSL)(JSSE)
- Define system property named
https.port
:
-Dhttps.port = 9000 -Dhttps.keyStore=/path/to/jks -Dhttps.keyStorePassword=password
- TODO
- Configuration in
application.conf
:play.i18n.langs = ["en", "zh"]
- Create `messages` file for each `langs`, i.e. `messages.en-US` & `messages.zh-CN`, and configuration:
```shell
auth.unknown = Unknown author: {0}
password.unknown = Unknown password: {0}
- Using
MessageApi
:val msgString = Messages("items.found", items.size)
- `Accept-Language` in the `HTTP headers`:
`Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3`
so you can using following choice lang:
```scala
request.headers.replace(("Accept-Language", "en-US"))
- Add a library dependency on play-mailer:
"com.typesafe.play" %% "play-mailer" % "3.0.1"
- Configuration in `application.conf`:
```shell
play.mailer {
host (mandatory)
port (defaults to 25)
ssl (defaults to no)
tls (defaults to no)
user (optional)
password (optional)
debug (defaults to no, to take effect you also need to set the log level to "DEBUG" for the application logger)
timeout (defaults to 60s)
connectiontimeout (defaults to 60s)
mock (defaults to no, will only log all the email properties instead of sending an email)
}