Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

play framework #25

Open
wangbinyq opened this issue Nov 6, 2018 · 7 comments
Open

play framework #25

wangbinyq opened this issue Nov 6, 2018 · 7 comments
Labels

Comments

@wangbinyq
Copy link
Owner

Concept

  • Configuration API
  • HTTP Programming
  • Asynchronous HTTP Programming
  • Form submission and validation
  • Working with Json
  • Accessing an SQL database
  • Dependency Injection
  • Application Setting
  • Testing
  • Logging
@wangbinyq
Copy link
Owner Author

Configuration API

Typesafe config library

Accessing the configure object

  • @Inject
  • ConfigureLoader: custom config type

@wangbinyq
Copy link
Owner Author

wangbinyq commented Nov 6, 2018

HTTP Programming

  • Action: (play.api.mvc.Request => play.api.mvc.Result)
  • Controller: Action generator
  • Result
val ok = Ok("Hello world!")
val notFound = NotFound
val pageNotFound = NotFound(<h1>Page not found</h1>)
val badRequest = BadRequest(views.html.form(formWithErrors))
val oops = InternalServerError("Oops")
val anyStatus = Status(488)("Strange response type")
val redirect = Redirect("/user/home")
val todo = TODO

Routing

The router is a component that translating each incoming HTTP request to an Action.

Routes are defined in the conf/routes file

  • routes file syntax
GET   /clients/:id          controllers.Clients.show(id: Long)
POST  /api/new              controllers.Api.newThing
->      /api                        api.MyRouter # prefix /api
  • Reversing routing
# Hello action
GET   /hello/:name          controllers.Application.hello(name)

// Redirect to /hello/Bob
def helloBob = Action {
  Redirect(routes.Application.hello("Bob"))
}
  • default routing
# Redirects to https://www.playframework.com/ with 303 See Other
GET   /about      controllers.Default.redirect(to = "https://www.playframework.com/")

# Responds with 404 Not Found
GET   /orders     controllers.Default.notFound

# Responds with 500 Internal Server Error
GET   /clients    controllers.Default.error

# Responds with 501 Not Implemented
GET   /posts      controllers.Default.todo

Body parsers

  • Play use Akka Streams to read request body. It's asynchronous!
  • Precise Action type
trait Action[A] extends (Request[A] => Result) {
  def parser: BodyParser[A]
}

trait Request[+A] extends RequestHeader {
  def body: A
}
  • default body parser: AnyContent
    • asJson -> Option[JsValue] (application/json)
    • asFormUrlEncoded -> Option[Map[String, Seq[String]]] (application/x-www-form-urlencoded)
    • asMultipartFormData, asRaw, asText, asXml
  • explicit body parser: Request[JsValue]

Action Composition

Add functionality to Action.

  • Action with logging
case class Logging[A](action: Action[A]) extends Action[A] {

  def apply(request: Request[A]): Future[Result] = {
    Logger.info("Calling action")
    action(request)
  }

  override def parser = action.parser
  override def executionContext = action.executionContext
}
  • Action with authentication

Error Handling

  • onClientError
  • onServerError
  • play.http.errorHandler = "com.example.ErrorHandler"

@wangbinyq
Copy link
Owner Author

wangbinyq commented Nov 6, 2018

Asynchronous HTTP Programming

Controller action should avoid block operations.

  • Action.async
  • Future[Result]

Streaming HTTP Response

  • Ok.sendFile

WebSocket

@wangbinyq
Copy link
Owner Author

wangbinyq commented Nov 6, 2018

JSON Basic

Library

  • Automatic conversion to and from case classes with minimal boilerplate
  • Custom validation while parsing

Converting to a JsValue

  • Json.parse
  • class construction
  • writer converter
// built-in writer converter
import play.api.libs.json._

// basic types
val jsonString = Json.toJson("Fiver")
val jsonNumber = Json.toJson(4)
val jsonBoolean = Json.toJson(false)

// collections of basic types
val jsonArrayOfInts = Json.toJson(Seq(1, 2, 3, 4))
val jsonArrayOfStrings = Json.toJson(List("Fiver", "Bigwig"))
// custom writer converter

case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
import play.api.libs.json._

implicit val locationWrites = new Writes[Location] {
  def writes(location: Location) = Json.obj(
    "lat" -> location.lat,
    "long" -> location.long
  )
}

implicit val residentWrites = new Writes[Resident] {
  def writes(resident: Resident) = Json.obj(
    "name" -> resident.name,
    "age" -> resident.age,
    "role" -> resident.role
  )
}

implicit val placeWrites = new Writes[Place] {
  def writes(place: Place) = Json.obj(
    "name" -> place.name,
    "location" -> place.location,
    "residents" -> place.residents
  )
}

val place = Place(
  "Watership Down",
  Location(51.235685, -1.309197),
  Seq(
    Resident("Fiver", 4, None),
    Resident("Bigwig", 6, Some("Owsla"))
  )
)

val json = Json.toJson(place)
// writer in combinator pattern

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
  (JsPath \ "long").write[Double]
)(unlift(Location.unapply))

implicit val residentWrites: Writes[Resident] = (
  (JsPath \ "name").write[String] and
  (JsPath \ "age").write[Int] and
  (JsPath \ "role").writeNullable[String]
)(unlift(Resident.unapply))

implicit val placeWrites: Writes[Place] = (
  (JsPath \ "name").write[String] and
  (JsPath \ "location").write[Location] and
  (JsPath \ "residents").write[Seq[Resident]]
)(unlift(Place.unapply))

Converting from a JsValue

  • Json.stringify
  • JsValue to model with Reads[T]
case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationReads: Reads[Location] = (
  (JsPath \ "lat").read[Double] and
  (JsPath \ "long").read[Double]
)(Location.apply _)

implicit val residentReads: Reads[Resident] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "age").read[Int] and
  (JsPath \ "role").readNullable[String]
)(Resident.apply _)

implicit val placeReads: Reads[Place] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "location").read[Location] and
  (JsPath \ "residents").read[Seq[Resident]]
)(Place.apply _)

val json = { ... }

val placeResult: JsResult[Place] = json.validate[Place]
// JsSuccess(Place(...),)

val residentResult: JsResult[Resident] = (json \ "residents")(1).validate[Resident]
// JsSuccess(Resident(Bigwig,6,Some(Owsla)),)

JSON With HTTP

JSON automated mapping

import play.api.libs.json._
case class Resident(name: String, age: Int, role: Option[String])
implicit val residentReads = Json.reads[Resident]

@wangbinyq
Copy link
Owner Author

wangbinyq commented Nov 6, 2018

Play Slick

@wangbinyq
Copy link
Owner Author

wangbinyq commented Nov 6, 2018

Dependency Injection

Guice(Runtime DI)
Guide to DI in scala(Compile Time DI)

  • @Inject
    • Constructor Injection
    • Method Injection
    • Field Injection
    • Optional Injection
  • Component Lifecycle
    • New instances are created every time a component is needed. Use @singleton
    • Instances are created lazily when they are needed.
    • Instances are not automatically cleaned up,

@wangbinyq
Copy link
Owner Author

wangbinyq commented Nov 14, 2018

Form and Validation

https://github.com/playframework/play-scala-rest-api-example

import play.api.data._
import play.api.data.Form._
play.api.data.validation.Constraints._
  • define a form (map form model)
  • define constraints in the form
  • validating the form in an action
  • displaying the form in a view template (optional)
  • processing the result (or errors) of the form

@wangbinyq wangbinyq added the note label Nov 28, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant