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. 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
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. 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
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
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. 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. 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. 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. 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
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
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
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
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. 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. 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
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. 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. 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. 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