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
[master]: Stop Netty server reloading configuration on each request #8335
Merged
richdougherty
merged 7 commits into
playframework:master
from
richdougherty:netty-conf-reload
Apr 18, 2018
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
9e48646
Stop Netty server reloading configuration on each request
richdougherty a65a5e8
Don't use Try.fold as it's not available in Scala 2.11
richdougherty 9e7f582
Add migration note
richdougherty 2302ca0
Instrument server reloads, add tests and fix buggy reloads
richdougherty aeb3bae
Add deprecation to ApplicationProvider.current
richdougherty 8090e63
Add MiMa filters
richdougherty 09a10f4
Migration guide changes
richdougherty File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,11 +18,9 @@ import play.api.http._ | |
import play.api.libs.streams.Accumulator | ||
import play.api.mvc._ | ||
import play.api.{ Application, Logger } | ||
import play.core.ApplicationProvider | ||
import play.core.server.{ NettyServer, Server } | ||
import play.core.server.common.{ ReloadCache, ServerResultUtils } | ||
import play.core.server.common.{ ReloadCache, ServerDebugInfo, ServerResultUtils } | ||
|
||
import scala.compat.java8.FutureConverters | ||
import scala.concurrent.Future | ||
import scala.util.{ Failure, Success, Try } | ||
|
||
|
@@ -48,7 +46,8 @@ private[play] class PlayRequestHandler(val server: NettyServer, val serverHeader | |
*/ | ||
private case class ReloadCacheValues( | ||
resultUtils: ServerResultUtils, | ||
modelConversion: NettyModelConversion | ||
modelConversion: NettyModelConversion, | ||
serverDebugInfo: Option[ServerDebugInfo] | ||
) | ||
|
||
/** | ||
|
@@ -61,7 +60,8 @@ private[play] class PlayRequestHandler(val server: NettyServer, val serverHeader | |
val modelConversion = new NettyModelConversion(serverResultUtils, forwardedHeaderHandler, serverHeader) | ||
ReloadCacheValues( | ||
resultUtils = serverResultUtils, | ||
modelConversion = modelConversion | ||
modelConversion = modelConversion, | ||
serverDebugInfo = reloadDebugInfo(tryApp, NettyServer.provider) | ||
) | ||
} | ||
} | ||
|
@@ -81,43 +81,43 @@ private[play] class PlayRequestHandler(val server: NettyServer, val serverHeader | |
import play.core.Execution.Implicits.trampoline | ||
|
||
val tryApp: Try[Application] = server.applicationProvider.get | ||
val cacheValues: ReloadCacheValues = reloadCache.cachedFrom(tryApp) | ||
|
||
val tryRequest: Try[RequestHeader] = modelConversion(tryApp).convertRequest(channel, request) | ||
val tryRequest: Try[RequestHeader] = cacheValues.modelConversion.convertRequest(channel, request) | ||
|
||
def clientError(statusCode: Int, message: String) = { | ||
// Helper to attach ServerDebugInfo attribute to a RequestHeader | ||
def attachDebugInfo(rh: RequestHeader): RequestHeader = { | ||
ServerDebugInfo.attachToRequestHeader(rh, cacheValues.serverDebugInfo) | ||
} | ||
|
||
def clientError(statusCode: Int, message: String): (RequestHeader, Handler) = { | ||
val unparsedTarget = modelConversion(tryApp).createUnparsedRequestTarget(request) | ||
val requestHeader = modelConversion(tryApp).createRequestHeader(channel, request, unparsedTarget) | ||
val result = errorHandler(server.applicationProvider.current).onClientError(requestHeader, statusCode, | ||
val debugHeader = attachDebugInfo(requestHeader) | ||
val result = errorHandler(tryApp).onClientError(debugHeader, statusCode, | ||
if (message == null) "" else message) | ||
// If there's a problem in parsing the request, then we should close the connection, once done with it | ||
requestHeader -> Left(result.map(_.withHeaders(HeaderNames.CONNECTION -> "close"))) | ||
debugHeader -> Server.actionForResult(result.map(_.withHeaders(HeaderNames.CONNECTION -> "close"))) | ||
} | ||
|
||
val (requestHeader, resultOrHandler) = tryRequest match { | ||
|
||
val (requestHeader, handler): (RequestHeader, Handler) = tryRequest match { | ||
case Failure(exception: TooLongFrameException) => clientError(Status.REQUEST_URI_TOO_LONG, exception.getMessage) | ||
case Failure(exception) => clientError(Status.BAD_REQUEST, exception.getMessage) | ||
case Success(untagged) => | ||
Server.getHandlerFor(untagged, tryApp) match { | ||
|
||
case Left(directResult) => | ||
untagged -> Left(directResult) | ||
|
||
case Right((taggedRequestHeader, handler, application)) => | ||
taggedRequestHeader -> Right((handler, application)) | ||
} | ||
|
||
val debugHeader: RequestHeader = attachDebugInfo(untagged) | ||
Server.getHandlerFor(debugHeader, tryApp) | ||
} | ||
|
||
resultOrHandler match { | ||
handler match { | ||
|
||
//execute normal action | ||
case Right((action: EssentialAction, app)) => | ||
handleAction(action, requestHeader, request, Some(app)) | ||
case action: EssentialAction => | ||
handleAction(action, requestHeader, request, tryApp) | ||
|
||
case Right((ws: WebSocket, app)) if requestHeader.headers.get(HeaderNames.UPGRADE).exists(_.equalsIgnoreCase("websocket")) => | ||
case ws: WebSocket if requestHeader.headers.get(HeaderNames.UPGRADE).exists(_.equalsIgnoreCase("websocket")) => | ||
logger.trace("Serving this request with: " + ws) | ||
|
||
val app = tryApp.get // Guaranteed to be Success for a WebSocket handler | ||
val wsProtocol = if (requestHeader.secure) "wss" else "ws" | ||
val wsUrl = s"$wsProtocol://${requestHeader.host}${requestHeader.path}" | ||
val bufferLimit = app.configuration.getDeprecated[ConfigMemorySize]("play.server.websocket.frame.maxLength", "play.websocket.buffer.limit").toBytes.toInt | ||
|
@@ -130,7 +130,7 @@ private[play] class PlayRequestHandler(val server: NettyServer, val serverHeader | |
case Left(result) => | ||
// WebSocket was rejected, send result | ||
val action = EssentialAction(_ => Accumulator.done(result)) | ||
handleAction(action, requestHeader, request, Some(app)) | ||
handleAction(action, requestHeader, request, tryApp) | ||
case Right(flow) => | ||
import app.materializer | ||
val processor = WebSocketHandler.messageFlowToFrameProcessor(flow, bufferLimit) | ||
|
@@ -141,32 +141,26 @@ private[play] class PlayRequestHandler(val server: NettyServer, val serverHeader | |
case error => | ||
app.errorHandler.onServerError(requestHeader, error).flatMap { result => | ||
val action = EssentialAction(_ => Accumulator.done(result)) | ||
handleAction(action, requestHeader, request, Some(app)) | ||
handleAction(action, requestHeader, request, tryApp) | ||
} | ||
} | ||
|
||
//handle bad websocket request | ||
case Right((ws: WebSocket, app)) => | ||
case ws: WebSocket => | ||
logger.trace("Bad websocket request") | ||
val action = EssentialAction(_ => Accumulator.done( | ||
Results.Status(Status.UPGRADE_REQUIRED)("Upgrade to WebSocket required").withHeaders( | ||
HeaderNames.UPGRADE -> "websocket", | ||
HeaderNames.CONNECTION -> HeaderNames.UPGRADE | ||
) | ||
)) | ||
handleAction(action, requestHeader, request, Some(app)) | ||
handleAction(action, requestHeader, request, tryApp) | ||
|
||
// This case usually indicates an error in Play's internal routing or handling logic | ||
case Right((h, _)) => | ||
case h => | ||
val ex = new IllegalStateException(s"Netty server doesn't handle Handlers of this type: $h") | ||
logger.error(ex.getMessage, ex) | ||
throw ex | ||
|
||
case Left(e) => | ||
logger.trace("No handler, got direct result: " + e) | ||
val action = EssentialAction(_ => Accumulator.done(e)) | ||
handleAction(action, requestHeader, request, None) | ||
|
||
} | ||
} | ||
|
||
|
@@ -264,12 +258,13 @@ private[play] class PlayRequestHandler(val server: NettyServer, val serverHeader | |
* Handle an essential action. | ||
*/ | ||
private def handleAction(action: EssentialAction, requestHeader: RequestHeader, | ||
request: HttpRequest, app: Option[Application]): Future[HttpResponse] = { | ||
implicit val mat: Materializer = app.fold(server.materializer)(_.materializer) | ||
request: HttpRequest, tryApp: Try[Application]): Future[HttpResponse] = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line is the key fix. We're ensuring we pass the original |
||
implicit val mat: Materializer = tryApp match { | ||
case Success(app) => app.materializer | ||
case Failure(_) => server.materializer | ||
} | ||
import play.core.Execution.Implicits.trampoline | ||
|
||
val tryApp = Try(app.get) | ||
|
||
// Execute the action on the Play default execution context | ||
val actionFuture = Future(action(requestHeader))(mat.executionContext) | ||
for { | ||
|
@@ -283,24 +278,27 @@ private[play] class PlayRequestHandler(val server: NettyServer, val serverHeader | |
}.recoverWith { | ||
case error => | ||
logger.error("Cannot invoke the action", error) | ||
errorHandler(app).onServerError(requestHeader, error) | ||
errorHandler(tryApp).onServerError(requestHeader, error) | ||
} | ||
// Clean and validate the action's result | ||
validatedResult <- { | ||
val cleanedResult = resultUtils(tryApp).prepareCookies(requestHeader, actionResult) | ||
resultUtils(tryApp).validateResult(requestHeader, cleanedResult, errorHandler(app)) | ||
resultUtils(tryApp).validateResult(requestHeader, cleanedResult, errorHandler(tryApp)) | ||
} | ||
// Convert the result to a Netty HttpResponse | ||
convertedResult <- modelConversion(tryApp) | ||
.convertResult(validatedResult, requestHeader, request.protocolVersion(), errorHandler(app)) | ||
.convertResult(validatedResult, requestHeader, request.protocolVersion(), errorHandler(tryApp)) | ||
} yield convertedResult | ||
} | ||
|
||
/** | ||
* Get the error handler for the application. | ||
*/ | ||
private def errorHandler(app: Option[Application]): HttpErrorHandler = | ||
app.fold[HttpErrorHandler](DefaultHttpErrorHandler)(_.errorHandler) | ||
private def errorHandler(tryApp: Try[Application]): HttpErrorHandler = | ||
tryApp match { | ||
case Success(app) => app.errorHandler | ||
case Failure(_) => DefaultHttpErrorHandler | ||
} | ||
|
||
/** | ||
* Sends a simple response with no body, then closes the connection. | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍