こわくないよ❤️ playframeworkソースコードリーディング入門

88
Playframework 1

Upload: tanacasino

Post on 16-Apr-2017

1.019 views

Category:

Technology


0 download

TRANSCRIPT

Playframework

1

1.

2.

3.

4.

3 4 4

3 20:00

2

3

@tanacasino

Scala 2 EucalytpusOpenStack

GitBucket (Committer)

4

GitHub5

6

7

documentation

framework

templates

Play

Lightbend HP play-scala

8

framework/src

build-link play-functional play-netty-utilsfork-run play-integration-test play-serverfork-run-protocol play-java play-specs2iteratees play-java-jdbc play-streamsplay play-java-jpa play-testplay-akka-http-server play-java-ws play-wsplay-cache play-jdbc routes-compilerplay-datacommons play-jdbc-api run-supportplay-docs play-jdbc-evolutions sbt-fork-run-pluginplay-docs-sbt-plugin play-json sbt-pluginplay-exceptions play-logbackplay-filters-helpers play-netty-server

9

10

1.

2. Action Controller

3. Json

4. Applicative

11

12

main def main(args: Array[String]): Unit

13

main

ag 'def main\(' src

3

fork-run sbt

src/fork-run/src/main/scala/play/forkrun/ForkRun.scala28: def main(args: Array[String]): Unit = {

src/play-netty-server/src/main/scala/play/core/server/NettyServer.scala298: def main(args: Array[String]) {

src/play-server/src/main/scala/play/core/server/ProdServerStart.scala20: def main(args: Array[String]) {

14

NettyServer

def main(args: Array[String]) { System.err.println( s"NettyServer.main is deprecated. \ Please start your Play server with the \ ${ProdServerStart.getClass.getName}.main." ) ProdServerStart.main(args) }

ProdServerStart

15

main grep

play-scala

sbt dist

ProdServerStart

declare -a app_mainclass=("play.core.server.ProdServerStart")# java -jar play.core.server.ProdServerStart

16

framework/src/play-server/src/main/scala/play/core/server/ProdServerStart.scala

2

object ProdServerStart { /** * Start a prod mode server from the command line. */ def main(args: Array[String]) { val process = new RealServerProcess(args) start(process) } // ...} 17

RealServerProcess

ServerProcess JVM

/** * Abstracts a JVM process so it can be mocked for testing or to * isolate pseudo-processes within a VM. Code using this class * should use the methods in this class instead of methods like * `System.getProperties()`, `System.exit()`, etc. */trait ServerProcess

class RealServerProcess( val args: Seq[String]) extends ServerProcess

18

ServerProcess

trait ServerProcess { /** The ClassLoader that should be used */ def classLoader: ClassLoader /** The command line arguments the process as invoked with */ def args: Seq[String] /** The process's system properties */ def properties: Properties /** Helper for getting properties */ final def prop(name: String): Option[String] = Option(properties.getProperty(name)) /** The process's id */ def pid: Option[String] /** Add a hook to run when the process shuts down */ def addShutdownHook(hook: => Unit): Unit /** Exit the process with a message and optional cause and return code */ def exit(message: String, cause: Option[Throwable] = None, returnCode: } 19

RealServerProcess

pid

class RealServerProcess(val args: Seq[String]) extends ServerProcess def classLoader: ClassLoader = Thread.currentThread.getContextClassLoader def properties: Properties = System.getProperties def pid: Option[String] = ManagementFactory.getRuntimeMXBean .getName.split('@').headOption def addShutdownHook(hook: => Unit): Unit = { Runtime.getRuntime.addShutdownHook(new Thread { override def run() = hook }) } def exit(message: String, cause: Option[Throwable] = None, returnCode: /* ... */ }}

20

ProdServerStart#start

def main(args: Array[String]): Unit = { val process = new RealServerProcess(args) // start(process)}// 4def start(process: ServerProcess): ServerWithStop = { try { // 1. Read settings val config: ServerConfig = readServerConfigSettings(process) // 2. Create a PID file before we do any real work val pidFile = createPidFile(process, config.configuration) // 3. Start the application val application: Application = { /* */ } Play.start(application) // 4. Start the server val serverProvider: ServerProvider = ServerProvider.fromConfiguration(process.classLoader, config.configuration) val server = serverProvider.createServer(config, application) // ... }

21

ProdServerStart#start

1.

2. PID(Process ID)

3. play.api.Application

4. HTTP

22

1. src

!(option).isDefined (option).isEmpty

def readServerConfigSettings(process: ServerProcess): ServerConfig = { val configuration: Configuration = { /* */ } val rootDir: File = { /* */ } def parsePort(portType: String): Option[Int] = { // configuration http/https parse } val httpPort = parsePort("http") val httpsPort = parsePort("https") if (!(httpPort orElse httpsPort).isDefined) throw ServerStartException val address = configuration.getString("play.server.http.address") .getOrElse("0.0.0.0") ServerConfig(/* */)}

23

{} rootDirArg

val configuration: Configuration = { // rootDir Some(java.io.File) None val rootDirArg: Option[File] = process.args.headOption.map(new File(_)) // Map.empty // play.server.dir // "play.server.dir" -> "${dir.getAbsolutePath}" Map val rootDirConfig = rootDirArg.fold(Map.empty[String, String]) (dir => ServerConfig.rootDirConfig(dir)) // Configuration . Configuration.load( process.classLoader, process.properties, rootDirConfig, true )}

24

play.api.Con�guration

object Configuration { private[play] def load( classLoader: ClassLoader, properties: Properties, directSettings: Map[String, AnyRef], allowMissingApplicationConf: Boolean): Configuration = { try { // 1. SystemProperty val systemPropertyConfig = /* */ // 2. Map[String, String] val directConfig: Config = ConfigFactory.parseMap(directSettings.asJava) // 3. conf/application.conf val applicationConfig: Config = /* */ // 4. play overrides conf val playOverridesConfig: Config = ConfigFactory.parseResources(classLoader, // 5. reference.conf val referenceConfig: Config = ConfigFactory.parseResources(classLoader, // ... 25

5

1. SystemProperty (System.getProperty)

2. DirectCon�g:

3. conf/application.conf )

4. play overrides con�g

Akka Play

5. reference.conf

Play

26

Combine

reduceLeft(_ withFallback _)

// Combine all the config together into one big config val combinedConfig: Config = Seq( systemPropertyConfig, directConfig, applicationConfig, playOverridesConfig, referenceConfig ).reduceLeft(_ withFallback _)

// resolve ${foo.bar} // val resolvedConfig = combinedConfig.resolve Configuration(resolvedConfig) } catch { case e: ConfigException => throw configError(e.origin, e.getMessage, }

27

rootDir

Option.get getOrElse(throw new Exception)

val rootDir: File = { // play.server.dir val path = configuration .getString("play.server.dir") .getOrElse(throw ServerStartException("No root server path supplied" val file = new File(path) if (!(file.exists && file.isDirectory)) { throw ServerStartException(s"Bad root server path: $path") } file}

28

http/https port

http/https

def parsePort(portType: String): Option[Int] = { configuration.getString(s"play.server.${portType}.port").flatMap { case "disabled" => None case str => val i = try Integer.parseInt(str) catch { case _: NumberFormatException => throw ServerStartException( } Some(i) }}

val httpPort = parsePort("http")val httpsPort = parsePort("https")if (!(httpPort orElse httpsPort).isDefined) throw ServerStartException

29

TypeSafe Con�g

5

reduceLeft Combine

{}

Option.get getOrElse(throw newException)

30

2. PID src

// 2. Create a PID file before we do any real workval pidFile = createPidFile(process, config.configuration)

def createPidFile(process: ServerProcess, configuration: Configuration // "play.server.pidfile.path" val pidFilePath = configuration .getString("play.server.pidfile.path") .getOrElse(throw ServerStartException("Pid file path not configured" // "play.server.pidfile.path" "/dev/null" if (pidFilePath == "/dev/null") None else { val pidFile = new File(pidFilePath).getAbsoluteFile // PID if (pidFile.exists) { throw ServerStartException(s"This application is already running (Or delete } val pid = process.pid getOrElse (throw ServerStartException("Couldn't determine current process's pid" val out = new FileOutputStream(pidFile) try out.write(pid.getBytes) finally out.close() Some(pidFile) }} 31

3. Application src

play.api.Application UT

// 3. Start the applicationval application: Application = { val environment = Environment(config.rootDir, process.classLoader, Mode.Prod) val context = ApplicationLoader.createContext(environment) val loader = ApplicationLoader(context) loader.load(context)}Play.start(application)

32

play.api.Environment

case class

/** * The environment for the application. * * Captures concerns relating to the classloader and * the filesystem for the application. * * @param * @param classLoader * @param mode Prod/Dev/Test 3 ) */case class Environment( rootPath: File, classLoader: ClassLoader, mode: Mode.Mode)

33

ApplicationLoader#createContext

SourceMapper:

WebCommands: Evolutions

/** * Create an application loading context. * Locates and loads the necessary configuration files for the application. */def createContext(environment: Environment, initialSettings: Map[String, AnyRef] = Map.empty[String, AnyRef], sourceMapper: Option[SourceMapper] = None, webCommands: WebCommands = new DefaultWebCommands) = { val configuration = Configuration.load(environment, initialSettings) Context(environment, sourceMapper, webCommands, configuration)}

34

ApplicationLoader#apply

Scala/Java

// Locate and instantiate the ApplicationLoader.def apply(context: Context): ApplicationLoader = { Reflect.configuredClass[ ApplicationLoader, play.ApplicationLoader, GuiceApplicationLoader ]( context.environment, PlayConfig(context.initialConfiguration), "play.application.loader", classOf[GuiceApplicationLoader].getName ) match { case None => /* */ new GuiceApplicationLoader /* */ case Some(Left(scalaClass)) => /* Scala */ scalaClass.newInstance case Some(Right(javaClass)) => /* Java */ javaClass.newInstance }} 35

ApplicationLoader

ApplicationLoader

Play Runtime DI Compile time DI

play.application.loader ApplicationLoaderCompile time DI

Scala trait Java interface

Guice Runtime DI

36

play.api.inject.guice.GuiceApplicationLoader

Guice

def this()

/** * An ApplicationLoader that uses Guice to bootstrap the application. * * Subclasses can override the `builder` and `overrides` methods. */class GuiceApplicationLoader( protected val initialBuilder: GuiceApplicationBuilder) extends ApplicationLoader {

// empty constructor needed for instantiating via reflection def this() = this(new GuiceApplicationBuilder)}

37

GuiceApplicationBuilder

Application Guice

final case class GuiceApplicationBuilder( environment: Environment = Environment.simple(), configuration: Configuration = Configuration.empty, modules: Seq[GuiceableModule] = Seq.empty, overrides: Seq[GuiceableModule] = Seq.empty, disabled: Seq[Class[_]] = Seq.empty, binderOptions: Set[BinderOption] = BinderOption.defaults, eagerly: Boolean = false, loadConfiguration: Environment => Configuration = Configuration.load, global: Option[GlobalSettings.Deprecated] = None, loadModules: (Environment, Configuration) => Seq[GuiceableModule] = ) extends GuiceBuilder[GuiceApplicationBuilder]( environment, configuration, modules, overrides, disabled, binderOptions, eagerly)

38

src

loader GuiceApplicationLoader

loader GuiceApplicationBuilder

loader.load

// Start the applicationval application: Application = { val environment = Environment(config.rootDir, process.classLoader, val context = ApplicationLoader.createContext(environment) val loader = ApplicationLoader(context) // loader.load(context)}

39

GuiceApplicationLoader#load

GuiceApplicationBuilder build

builder Con�g Environment

override final def load( context: ApplicationLoader.Context): Application = { builder(context).build}protected def builder( context: ApplicationLoader.Context): GuiceApplicationBuilder = { initialBuilder.disableCircularProxies() .in(context.environment) .loadConfig(context.initialConfiguration) .overrides(overrides(context): _*)}protected def overrides(context: ApplicationLoader.Context): Seq[GuiceableModule GuiceApplicationLoader.defaultOverrides(context)} 40

GuiceApplicationLoader#defaultOverrides

Guice Module

object GuiceApplicationLoader { /** * The default overrides provided by the Scala and Java GuiceApplicationLoaders. */ def defaultOverrides( context: ApplicationLoader.Context ): Seq[GuiceableModule] = { Seq( bind[OptionalSourceMapper] to new OptionalSourceMapper(context.sourceMapper), bind[WebCommands] to context.webCommands, bind[DefaultApplicationLifecycle] to context.lifecycle) }}

41

GuiceApplicationBuilder#build

injector Application

injector(): play.api.injector.PlayInjector

def injector Guice Injector

override final def load( context: ApplicationLoader.Context): Application = { builder(context).build // }

/** * Create a new Play Application using this configured builder. */def build(): Application = injector().instanceOf[Application]

42

GuiceBuilder#injector

GuiceApplicationBuilder GuiceBuilder

Guice Injector

/** * Create a Play Injector backed by Guice using this configured builder. */ def injector(): PlayInjector = { try { val stage = /* */ // Injector(com.google.inject.Injector) val guiceInjector = Guice.createInjector(stage, applicationModule()) // Injector PlayInjector guiceInjector.getInstance(classOf[PlayInjector]) } catch { // ... } } 43

GuiceBuilder#applicationModule

PlayInjector GuiceInjector

conf Module

// Create a Play Injector backed by Guice using this configured builder. def applicationModule(): GuiceModule = createModule()

def createModule(): GuiceModule = { import scala.collection.JavaConverters._ val injectorModule = GuiceableModule.guice(Seq( bind[PlayInjector].to[GuiceInjector], bind[play.inject.Injector].to[play.inject.DelegateInjector] ), binderOptions) val enabledModules = modules.map(_.disable(disabled)) val bindingModules = GuiceableModule.guiced(environment, configuration, binderOptions)(enabledModules) :+ injectorModule val overrideModules = GuiceableModule.guiced(environment, configuration, binderOptions)(overrides) GuiceModules.`override`(bindingModules.asJava).`with`(overrideModules.asJava) }

44

GuiceInjector

GuiceInjector Guice Injector

instanceOf[T]

injector().instanceOf[Application]

class GuiceInjector @Inject() ( injector: com.google.inject.Injector) extends PlayInjector { def instanceOf[T](implicit ct: ClassTag[T]) = instanceOf(ct.runtimeClass.asInstanceOf[ def instanceOf[T](clazz: Class[T]) = injector.getInstance(clazz) def instanceOf[T](key: BindingKey[T]) = injector.getInstance(GuiceKey}

45

Application

Guice Injector con�g

framework/src/play/src/main/resources/reference.conf

play { modules { # The enabled modules that should be automatically loaded. enabled += "play.api.inject.BuiltinModule" # enabled += "play.api.i18n.I18nModule"

# A way to disable modules that are automatically enabled disabled = [] }}

46

play.api.inject.BuiltinModule

Provider

class BuiltinModule extends Module { def bindings(env: Environment, configuration: Configuration): Seq[ // ... ... Seq( bind[Environment] to env, bind[Configuration].toProvider[ConfigurationProvider], bind[DefaultApplicationLifecycle].toSelf, bind[ApplicationLifecycle].to(bind[DefaultApplicationLifecycle

bind[Application].to[DefaultApplication], // bind[play.Application].to[play.DefaultApplication], bind[ActorSystem].toProvider[ActorSystemProvider], bind[Materializer].toProvider[MaterializerProvider], bind[ExecutionContext].to[ExecutionContextExecutor], // ... ...} 47

DefaultApplication

Inject

@Singletonclass DefaultApplication @Inject() ( environment: Environment, applicationLifecycle: DefaultApplicationLifecycle, override val injector: Injector, override val configuration: Configuration, override val requestHandler: HttpRequestHandler, override val errorHandler: HttpErrorHandler, override val actorSystem: ActorSystem, override val materializer: Materializer) extends Application {

def path = environment.rootPath def classloader = environment.classLoader def mode = environment.mode def stop() = applicationLifecycle.stop()} 48

src

// 3. Start the applicationval application: Application = { val environment = Environment(config.rootDir, process.classLoader, Mode.Prod) val context = ApplicationLoader.createContext(environment) val loader = ApplicationLoader(context) loader.load(context)}// Play.start(application)

49

play.api.Play#start

@volatile private[play] var _currentApp: Application = _ def start(app: Application) { stop(_currentApp) _currentApp = app Threads.withContextClassLoader(app.classloader) { app.global.beforeStart(app) app.routes app.global.onStart(app) } app.mode match { case Mode.Test => // started case mode => logger.info("Application started (" + mode + ")") } }

50

Application

ApplicationLoader Application

ApplicationLoader

Guice

GuiceApplicationLoader

GuiceApplicationBuilder

BuiltinModules

DefaultApplication

51

HTTP

netty src

ServerProvider Server

hookPID

val serverProvider: ServerProvider = ServerProvider.fromConfiguration( process.classLoader, config.configuration )val server = serverProvider.createServer(config, application)process.addShutdownHook { server.stop() pidFile.foreach(_.delete()) assert(!pidFile.exists(_.exists), "PID file should not exist!")}server

52

ServerProvider#fromCon�guration

play.server.provider new

framework/src/play-netty-server/src/main/resources/reference.conf

play.core.server.NettyServerProvider

akka-http experimental

def fromConfiguration(classLoader: ClassLoader, configuration: Configuration val ClassNameConfigKey = "play.server.provider" val className: String = configuration.getString(ClassNameConfigKey val clazz = try classLoader.loadClass(className) catch { // } val ctor = try clazz.getConstructor() catch { // } ctor.newInstance().asInstanceOf[ServerProvider]}

53

NettyServerProvider#createServer

play.core.server.NettyServer new

def createServer(context: ServerProvider.Context) = new NettyServer( context.config, context.appProvider, context.stopHook, context.actorSystem )( context.materializer )

54

NettyServer

val bindnew src

class NettyServer(/* */) { // Maybe the HTTP server channel private val httpChannel = config.port.map(bindChannel(_, secure = false)) // Maybe the HTTPS server channel private val httpsChannel = config.sslPort.map(bindChannel(_, secure = true)) private def bindChannel(port: Int, secure: Boolean): Channel = { val protocolName = if (secure) "HTTPS" else "HTTP" val address = new InetSocketAddress(config.address, port) val (serverChannel, channelSource) = bind(address) channelSource.runWith(channelSink(port = port, secure = secure)) /* */ }} 55

NettyServer#bind

netty akka-stream Source

HTTP SourceOK

Source => Sink

private def bind(address: InetSocketAddress): (Channel, Source[Channel val serverChannelEventLoop = eventLoop.next

// Watches for channel events, and pushes them through a reactive streams publisher. val channelPublisher = new HandlerPublisher(serverChannelEventLoop, classOf[ /* */ val channel = bootstrap.bind.await().channel() allChannels.add(channel)

(channel, Source.fromPublisher(channelPublisher)) }

56

NettyServer#channelSink

input(Source) Sink

PlayRequestHandler

val (serverChannel, channelSource) = bind(address)// channelSource.runWith(channelSink(port = port, secure = secure))// : private def channelSink(port: Int, secure: Boolean): Sink[Channel, Future Sink.foreach[Channel] { (connChannel: Channel) => val pipeline = connChannel.pipeline() /* ... */ pipeline.addLast("decoder", new HttpRequestDecoder(maxInitialLineLength, maxHeaderSize, maxChunkSize))

val requestHandler = new PlayRequestHandler(this) //

pipeline.addLast("http-handler", new HttpStreamsServerHandler(Seq pipeline.addLast("request-handler", requestHandler)      /* ... */ }} 57

NettyServer

akka-stream netty (Source/Sink)

play.server.netty.transport native epoll

(Linux)

PlayRequestHandler (

private lazy val transport = conf.getString("transport") match { case "native" => Native case "jdk" => Jdk}

58

59

ProdServerStart#main

Con�guration

ApplicationLoader/GuiceApplicationLoader/GuiceApplicationBuilder Application

Play.start Application

NettyServerProvider NettyServerHTTP

PlayRequestHandler

60

Typsafe Con�g/Google Guice/JBossNetty/Akka/Akka-Stream

sbt run

ProdServer

61

62

63

PlayRequestHandler

ChannelInboundHandlerAdapter netty class

Netty

def channelRead def handle

private[play] class PlayRequestHandler( val server: NettyServer) extends ChannelInboundHandlerAdapter { // ... override def channelRead(ctx: ChannelHandlerContext, msg: Object): // Handle the given request. def handle( channel: Channel, request: HttpRequest ): Future[HttpResponse] = { /* */ }} 64

PlayRequestHandler.channelRead

Netty HttpRequest handleWrite

trampoline ExecutionContext

override def channelRead(ctx: ChannelHandlerContext, msg: Object): Unit logger.trace(s"channelRead: ctx = ${ctx}, msg = ${msg}") msg match { case req: HttpRequest => requestsInFlight.incrementAndGet() /* handle */ val future: Future[HttpResponse] = handle(ctx.channel(), req) import play.api.libs.iteratee.Execution.Implicits.trampoline lastResponseSent = lastResponseSent.flatMap { /* */ ctx.writeAndFlush(httpResponse) /* Write */ } }}

65

PlayRequesthHandler.handle

NettyModelConversion Netty io.netty.handler.codec.http.HttpRequest Play play.api.mvc.RequestHeader

Try[RequestHeader]

def handle( channel: Channel, request: HttpRequest): Future[HttpResponse] = { import play.api.libs.iteratee.Execution.Implicits.trampoline val requestId = RequestIdProvider.requestIDs.incrementAndGet() // val tryRequest = modelConversion.convertRequest( requestId, channel.remoteAddress().asInstanceOf[InetSocketAddress], Option(channel.pipeline().get(classOf[SslHandler])), request )} 66

PlayRequestHandler.handle

Success server.getHandlerFor

Right Application handler

Left Result

val (requestHeader, resultOrHandler) = tryRequest match { case Failure(exception: TooLongFrameException) => clientError(Status case Failure(exception) => clientError(Status.BAD_REQUEST, exception.getMessage) case Success(untagged) => server.getHandlerFor(untagged) match { case Left(directResult) => untagged -> Left(directResult) case Right((taggedRequestHeader, handler, application)) => taggedRequestHeader -> Right((handler, application)) } }

67

play.core.server.Server#getHandleFor

Handler

Right Handler Left Result

WebCommand Left Result

DefaultHttpRequestHandler handleForRequest

def getHandlerFor( request: RequestHeader): Either[Future[Result], (RequestHeader, Handler, Application)] = { /* */ application.requestHandler.handlerForRequest(request) match { case (requestHeader, handler) => Right((requestHeader, handler, application)) }} 68

DefaultHttpRequestHandler#handleForRequest

HEAD GET

def handlerForRequest(request: RequestHeader) = { /* ... */ val (routedRequest, handler) = routeRequest(request) map { case handler: RequestTaggingHandler => (handler.tagRequest(request), handler) case otherHandler => (request, otherHandler) } getOrElse { // We automatically permit HEAD requests against any GETs without the need to // add an explicit mapping in Routes request.method match { case HttpVerbs.HEAD => routeRequest(request.copy(method = HttpVerbs.GET)) match { /* */ /* */ (routedRequest, filterHandler(rh => handler)(routedRequest))} 69

routeRequest, Router, RoutesProvider

class DefaultHttpRequestHandler { def routeRequest(request: RequestHeader): Option[Handler] = { router.handlerFor(request) }}

trait Router { def handlerFor(request: RequestHeader): Option[Handler] = { routes.lift(request) }}

class RoutesProvider { /* */ lazy val get = { val prefix = httpConfig.context val router = Router.load(environment, configuration) .fold[Router](Router.empty)(injector.instanceOf(_)) router.withPrefix(prefix) }}

70

routeRequest

Router BuiltinModule bind[Router].toProvider[RoutesProvider]

RoutesProvider#get routes-compiler

Routes routes

Gist

71

routes.Routes

conf/routes routes-compiler

target/scala-2.11/routes/main/router/Routes.scala

// Scalaclass Routes( override val errorHandler: play.api.http.HttpErrorHandler, // @LINE:6 HomeController_0: controllers.HomeController, // @LINE:8 CountController_3: controllers.CountController, // @LINE:10 AsyncController_2: controllers.AsyncController, // @LINE:13 Assets_1: controllers.Assets, val prefix: String) extends GeneratedRouter

72

Rourtes.routes

RequestHeader Handler PartialFunction

def routes: PartialFunction[RequestHeader, Handler] = { // @LINE:6 case controllers_HomeController_index0_route(params) => call { controllers_HomeController_index0_invoker.call( HomeController_0.index ) } // @LINE:8 case controllers_CountController_count1_route(params) => call { controllers_CountController_count1_invoker.call( CountController_3.count ) } /* */} 73

Handler src

val (requestHeader, resultOrHandler) = /* */

resultOrHandler match { //execute normal action case Right((action: EssentialAction, app)) => val recovered = EssentialAction { rh => import play.api.libs.iteratee.Execution.Implicits.trampoline action(rh).recoverWith { case error => app.errorHandler.onServerError(rh, error) } } handleAction(recovered, requestHeader, request, Some(app)) /* */ }

EssentialAction

EssentialAction { rh => action(rh)

74

EssentialAction

app/controllers/HomeController.scala

package controllersimport javax.inject.{ Inject, Singleton }import play.api.mvc.{ Controller, Action, AnyContent }@Singletonclass HomeController @Inject() extends Controller { def index: Action[AnyContent] = Action { req => Ok(views.html.index("Your new application is ready.")) }}

conf/routes

GET / controllers.HomeController.indexGET /count controllers.CountController.countGET /message controllers.AsyncController.message

75

Controller Action

def index: Action[AnyContent] = Action { req => Ok(views.html.index("Your new application is ready."))}

Action { req => object Action apply

trait Action[A]

A Action

Action[JsValue] JSON

req Request[A] A

Request Result

76

Action BodyParser

Action[A] BodyParser[A]

BodyParser[A] A

RequestHeader -> RequestBody(Array[Byte]) ->Result

RequestBody Asynchornous Stream

InputStream Akka-stream

77

Accumulator[ByteString, Result]

Action RequestHeader => Body => Result

Accumulator

RequestHeader => Accumulator[ByteString,Result]

Accumulator Akka-stream(Source)

ByteString Array[Byte] (Akka )

AsyncAsync FW

78

Action[A]

Action[A] Result

RequestHeader => Accumulator[ByteString, Result]

Play

What is EssentialAction

What is BodyParser79

EssentialAction

EssentialAction Handler MixIn

Action EssentialAction

EssentialAction RequestHeader => Accumulator[ByteString, Result]

trait EssentialAction extends (RequestHeader => Accumulator[ByteString, Result]) with Handler { self => def apply() = this}

trait Action[A] extends EssentialAction

80

: Function

Function

trait Function1[-T1, +R]

RequestHeader => Accumulator[ByteString, Result]

Funtion1[RequestHeader, Accumulator[ByteString,Result]]

// Intellij -T1 => R trait EssentialAction extends Function1[RequestHeader, Accumulator[ByteString, Result]]

81

Action#apply

BodyParser

def apply(request: Request[A]): Future[Result] = { /* */ } // def apply(rh: RequestHeader): Accumulator[ByteString, Result] = parser(rh).mapFuture { // parser BodyParser case Left(r) => /* ... (T_T) */ case Right(a) => /* */ val request = Request(rh, a) apply(request) // apply }(executionContext)

82

PlayRequestHandler

resultOrHandler match { //execute normal action case Right((action: EssentialAction, app)) => val recovered = EssentialAction { rh => import play.api.libs.iteratee.Execution.Implicits.trampoline // action(rh) Accumulator action(rh).recoverWith { case error => app.errorHandler.onServerError(rh, error) } } // handleAction(recovered, requestHeader, request, Some(app)) /* */ }

83

handleAction

Action

private def handleAction(action: EssentialAction, requestHeader: RequestHeader request: HttpRequest, app: Option[Application]): Future[HttpResponse for { bodyParser <- Future(action(requestHeader))(mat.executionContext) actionResult <- { val body = modelConversion.convertRequestBody(request) (body match { case None => bodyParser.run() case Some(source) => bodyParser.run(source) }).recoverWith { /* ... */ } } validatedResult <- { /* Clean and validate the action's result */ convertedResult <- { /* Netty HttpResponse */ modelConversion.convertResult(validatedResult, requestHeader, request.getProtocolVersion, errorHandler(app)) } } yield convertedResult}

84

handleAction

bodyParser Action applyAccumulator[ByteString, Result]

body NettyAccumulator run

actionResult ResultOk

Result Netty HttpResponse

85

src

HttpResponse writeAndFlush

override def channelRead(ctx: ChannelHandlerContext, msg: Object): Unit msg match { case req: HttpRequest => requestsInFlight.incrementAndGet() val future: Future[HttpResponse] = handle(ctx.channel(), req) /* handle */ /* */ import play.api.libs.iteratee.Execution.Implicits.trampoline lastResponseSent = lastResponseSent.flatMap { /* */ ctx.writeAndFlush(httpResponse) } }}

86

87

88