Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

2,436 views

Published on

https://d-cube.connpass.com/event/46199/
の発表資料です。
Playframework 2.5.10 のソースコードリーディング会

Published in: Technology
  • Login to see the comments

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

  1. 1. Playframework 1
  2. 2. 1. 2. 3. 4. 3 4 4 3 20:00 2
  3. 3. 3
  4. 4. @tanacasino Scala 2 Eucalytpus OpenStack GitBucket (Committer) 4
  5. 5. GitHub 5
  6. 6. 6
  7. 7. 7
  8. 8. documentation framework templates Play Lightbend HP play-scala 8
  9. 9. framework/src build-link play-functional play-netty-utils fork-run play-integration-test play-server fork-run-protocol play-java play-specs2 iteratees play-java-jdbc play-streams play play-java-jpa play-test play-akka-http-server play-java-ws play-ws play-cache play-jdbc routes-compiler play-datacommons play-jdbc-api run-support play-docs play-jdbc-evolutions sbt-fork-run-plugin play-docs-sbt-plugin play-json sbt-plugin play-exceptions play-logback play-filters-helpers play-netty-server 9
  10. 10. 10
  11. 11. 1. 2. Action Controller 3. Json 4. Applicative 11
  12. 12. 12
  13. 13. main def main(args: Array[String]): Unit 13
  14. 14. main ag 'def main(' src 3 fork-run sbt src/fork-run/src/main/scala/play/forkrun/ForkRun.scala 28: def main(args: Array[String]): Unit = { src/play-netty-server/src/main/scala/play/core/server/NettyServer.scala 298: def main(args: Array[String]) { src/play-server/src/main/scala/play/core/server/ProdServerStart.scala 20: def main(args: Array[String]) { 14
  15. 15. 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
  16. 16. main grep play-scala sbt dist ProdServerStart declare -a app_mainclass=("play.core.server.ProdServerStart") # java -jar play.core.server.ProdServerStart 16
  17. 17. framework/src/play- server/src/main/scala/play/core/server/ProdServe rStart.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
  18. 18. 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
  19. 19. 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.getProp /** 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
  20. 20. 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
  21. 21. ProdServerStart#start def main(args: Array[String]): Unit = { val process = new RealServerProcess(args) // start(process) } // 4 def 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( val server = serverProvider.createServer(config, application) // ... } 21
  22. 22. ProdServerStart#start 1. 2. PID(Process ID) 3. play.api.Application 4. HTTP 22
  23. 23. 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
  24. 24. {} rootDirArg val configuration: Configuration = { // rootDir Some(java.io.File) 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
  25. 25. 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.asJ // 3. conf/application.conf val applicationConfig: Config = /* */ // 4. play overrides conf val playOverridesConfig: Config = ConfigFactory.parseResources(class // 5. reference.conf val referenceConfig: Config = ConfigFactory.parseResources(classLoad // ... 25
  26. 26. 5 1. SystemProperty (System.getProperty) 2. DirectCon g: 3. conf/application.conf ) 4. play overrides con g Akka Play 5. reference.conf Play 26
  27. 27. 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
  28. 28. 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
  29. 29. 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
  30. 30. TypeSafe Con g 5 reduceLeft Combine {} Option.get getOrElse(throw new Exception) 30
  31. 31. 2. PID src // 2. Create a PID file before we do any real work val 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 // "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 } val pid = process.pid getOrElse (throw ServerStartException("Couldn't val out = new FileOutputStream(pidFile) try out.write(pid.getBytes) finally out.close() Some(pidFile) } } 31
  32. 32. 3. Application src play.api.Application UT // 3. Start the application val 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
  33. 33. 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
  34. 34. 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
  35. 35. 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
  36. 36. ApplicationLoader ApplicationLoader Play Runtime DI Compile time DI play.application.loader ApplicationLoader Compile time DI Scala trait Java interface Guice Runtime DI 36
  37. 37. 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
  38. 38. 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, ) 38
  39. 39. src loader GuiceApplicationLoader loader GuiceApplicationBuilder loader.load // Start the application val application: Application = { val environment = Environment(config.rootDir, process.classLoader, val context = ApplicationLoader.createContext(environment) val loader = ApplicationLoader(context) // loader.load(context) } 39
  40. 40. 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[Guiceable GuiceApplicationLoader.defaultOverrides(context) } 40
  41. 41. GuiceApplicationLoader#defaultOverrides Guice Module object GuiceApplicationLoader { /** * The default overrides provided by the Scala and Java GuiceApplication */ def defaultOverrides( context: ApplicationLoader.Context ): Seq[GuiceableModule] = { Seq( bind[OptionalSourceMapper] to new OptionalSourceMapper(context.sourc bind[WebCommands] to context.webCommands, bind[DefaultApplicationLifecycle] to context.lifecycle) } } 41
  42. 42. 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
  43. 43. 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
  44. 44. 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 val overrideModules = GuiceableModule.guiced(environment, configuratio GuiceModules.`override`(bindingModules.asJava).`with`(overrideModules. } 44
  45. 45. 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 def instanceOf[T](clazz: Class[T]) = injector.getInstance(clazz) def instanceOf[T](key: BindingKey[T]) = injector.getInstance(GuiceKey } 45
  46. 46. 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
  47. 47. 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
  48. 48. DefaultApplication Inject @Singleton class 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
  49. 49. src // 3. Start the application val 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
  50. 50. 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
  51. 51. Application ApplicationLoader Application ApplicationLoader Guice GuiceApplicationLoader GuiceApplicationBuilder BuiltinModules DefaultApplication 51
  52. 52. HTTP netty src ServerProvider Server hook PID 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
  53. 53. 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: Configurati 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
  54. 54. 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
  55. 55. NettyServer val bind new 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
  56. 56. NettyServer#bind netty akka-stream Source HTTP Source OK Source => Sink private def bind(address: InetSocketAddress): (Channel, Source[Channel val serverChannelEventLoop = eventLoop.next // Watches for channel events, and pushes them through a reactive stre val channelPublisher = new HandlerPublisher(serverChannelEventLoop, cl /* */ val channel = bootstrap.bind.await().channel() allChannels.add(channel) (channel, Source.fromPublisher(channelPublisher)) } 56
  57. 57. 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(maxInitialLineLengt val requestHandler = new PlayRequestHandler(this) // pipeline.addLast("http-handler", new HttpStreamsServerHandler(Seq pipeline.addLast("request-handler", requestHandler)      /* ... */ } } 57
  58. 58. 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. 59. 59
  60. 60. ProdServerStart#main Con guration ApplicationLoader/GuiceApplicationLoader/Guice ApplicationBuilder Application Play.start Application NettyServerProvider NettyServer HTTP PlayRequestHandler 60
  61. 61. Typsafe Con g/Google Guice/JBoss Netty/Akka/Akka-Stream sbt run ProdServer 61
  62. 62. 62
  63. 63. 63
  64. 64. 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
  65. 65. PlayRequestHandler.channelRead Netty HttpRequest handle Write 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
  66. 66. 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
  67. 67. 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.g case Success(untagged) => server.getHandlerFor(untagged) match { case Left(directResult) => untagged -> Left(directResult) case Right((taggedRequestHeader, handler, application)) => taggedRequestHeader -> Right((handler, application)) } } 67
  68. 68. 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, applic } } 68
  69. 69. DefaultHttpRequestHandler#handleForReque st 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 // 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
  70. 70. 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
  71. 71. routeRequest Router BuiltinModule bind[Router].toProvider[RoutesProvider] RoutesProvider#get routes-compiler Routes routes Gist 71
  72. 72. routes.Routes conf/routes routes-compiler target/scala-2.11/routes/main/router/Routes.scala // Scala class 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
  73. 73. 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
  74. 74. 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
  75. 75. EssentialAction app/controllers/HomeController.scala package controllers import javax.inject.{ Inject, Singleton } import play.api.mvc.{ Controller, Action, AnyContent } @Singleton class HomeController @Inject() extends Controller { def index: Action[AnyContent] = Action { req => Ok(views.html.index("Your new application is ready.")) } } conf/routes GET / controllers.HomeController.index GET /count controllers.CountController.count GET /message controllers.AsyncController.message 75
  76. 76. 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
  77. 77. Action BodyParser Action[A] BodyParser[A] BodyParser[A] A RequestHeader -> RequestBody(Array[Byte]) -> Result RequestBody Asynchornous Stream InputStream Akka- stream 77
  78. 78. Accumulator[ByteString, Result] Action RequestHeader => Body => Result Accumulator RequestHeader => Accumulator[ByteString, Result] Accumulator Akka-stream(Source) ByteString Array[Byte] ( Akka ) Async Async FW 78
  79. 79. Action[A] Action[A] Result RequestHeader => Accumulator[ByteString, Result] Play What is EssentialAction What is BodyParser 79
  80. 80. 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
  81. 81. : 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
  82. 82. 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
  83. 83. 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
  84. 84. handleAction Action private def handleAction(action: EssentialAction, requestHeader: RequestHe 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, reques } } yield convertedResult } 84
  85. 85. handleAction bodyParser Action apply Accumulator[ByteString, Result] body Netty Accumulator run actionResult Result Ok Result Netty HttpResponse 85
  86. 86. 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. 87. 87
  88. 88. 88

×