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
Deferred body parsing #9779
Deferred body parsing #9779
Conversation
logger.trace("Not deferring body parsing for request: " + rh) | ||
// Run the parser first and then call the action | ||
BodyParser.runParserThenInvokeAction(parser, rh, apply)(executionContext) | ||
} |
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.
I think it's best to start reviewing here in the apply
method. Basically when the request attribute exists, we know the parsing is deferred and therefore just skip the body parsing part, but start to execute the action.
The request attribute will be stored by the server backend.
Basically the original code of this apply
method just moved to runParserThenInvokeAction
.
} else { | ||
taggedRequestHeader | ||
})) | ||
val resultFuture: Future[Result] = invokeAction(futureAcc, deferBodyParsing) |
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.
Here in the server backend we decide if to run the body parser immediately (that would be supplied by the action if the request attribute does not get stored here) or if to skip it (which results in just running the action in Action.scala
).
Some notes:
I switched to trampoline
because it's a fast Future anyway so that should not really matter and actually the netty server backend also uses trampoline
. (So no need to also capture the executioncontext in the lambda)
if (deferBodyParsing) { | ||
acc.run() // don't parse anything | ||
} else { | ||
val body = modelConversion(tryApp).convertRequestBody(request) |
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.
I was thinking about moving this line val body = modelConversion(tryApp).convertRequestBody(request)
out of the lambda, and just capture the body
instead of the request
(to end up like in the akka-http backend to capture the source), but I think that does not matter. Edit: I kept it here to make keep it part of the exception handling.
@@ -274,6 +274,11 @@ object Server { | |||
|
|||
case object ServerStoppedReason extends CoordinatedShutdown.Reason | |||
|
|||
private[server] def routeModifierDefersBodyParsing(global: Boolean, rh: RequestHeader): Boolean = { |
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.
I made this package private, because I don't think someone should mess with it.
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.
Also I added this Attr
because both the server backends projects and the play core project need to access/share it.
@@ -289,31 +292,48 @@ private[play] class PlayRequestHandler( | |||
action: EssentialAction, | |||
requestHeader: RequestHeader, | |||
request: HttpRequest, | |||
tryApp: Try[Application] | |||
tryApp: Try[Application], | |||
deferredBodyParsingAllowed: Boolean = false |
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.
I added this deferredBodyParsingAllowed
only to not store the request attribute for the websocket error actions (but I think that would not even matter), just an optimization.
2d360f2
to
78331e1
Compare
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
documentation/manual/working/javaGuide/main/http/JavaActionsComposition.md
Outdated
Show resolved
Hide resolved
Summarizing the discussions we had about this feature: We do not want to pass
sealed trait LazyBody[A]
final case class DeferedBody[A](...?) extends LazyBody[A]
final case class Body[A](value: A) extends LazyBody[A] Note for this last implementation: I do think that we still need to parse the body explicitly by calling One more thing to take care of when implementing is Java's I guess I will go with the Some other notes which might be useful: |
5806e99
to
622cc23
Compare
Because I was not sure if storing a lambda (which captures the source) into a request attribute within the server backend (and carrying that object through) effects performance somehow, I did some benchmarking on my machine using the TechEmpower Framework Benchmark project with a local, custom Play build (Where I just rebased this pull request onto the 2.8.0 tag (not branch)). So performance wise I have a very good feeling we shouldn't have an issue here. |
Do we know what the status of this feature is? It is a very valuable one for some of us :) |
@bbatarelo My goal is to finish this pull request before Play 2.9 will be released. Also for me this feature is very useful. It is very high up on my TODO list. |
@mkurz exactly as the original issue of the ticket: preventing transparent alpakka streaming of multipart content to s3 in case authentication and/or other prerequisites aren't satisfied. |
@bbatarelo Good to hear others have the same thoughts and requirements like I do 😉 I definitely will push forward this pull request to include it into 2.9. What do you think if I provide a custom 2.8 release with this feature included (based on the current 2.8.2 release) Would you like to use such a custom release? It would probably be good for testing this feature (I think it's stable anyway). |
If it's not cumbersome to use meaning if I only need to change play plugin version or something like that, I'd start using it right away :) |
@bbatarelo No it's not cumbersome at all. You will just have to change the Play version to something like
I will build that custom release later today and let you know the details. Are you using Scala 2.13? |
That's good, thank you. Yes, I'm on 2.13. |
@bbatarelo It's done. I created a custom What you have to do (simple):
And you also have to add a resolver to both
That's it! Now you can enabled deferred body parsing in
Let me know if that works for you. Should be stable. |
fcfa28d
to
ccd38fd
Compare
@@ -0,0 +1,11 @@ | |||
/* | |||
* Copyright (C) Lightbend Inc. <https://www.lightbend.com> |
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.
Current copyright is below? 👀
Copyright (C) from 2022 The Play Framework Contributors https://github.com/playframework, 2011-2021 Lightbend Inc. https://www.lightbend.com
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.
I know, formatting still missing, I am currently testing this PR locally again and want to merge it at the absolutly last PR for 2.9 since I don't want to hold this feature back anymore and specially in the light of #11170 (and given the PR was finished basically anyway except)
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.
Roger that! I saw you force push and wondered if you were close to merging.
Co-Authored-By: Ignasi Marimon-Clos <ignasi@lightbend.com>
ccd38fd
to
6cfe730
Compare
When deferring is disabled (the default) we shouldn't use a lambda but just a normal method because it's faster (than lambdas/invokedynamic). Also akka-http performs much better with the ec of the materializer
f7ad9a8
to
831920a
Compare
cc8308f
to
613d3cc
Compare
So this is done. Actually the PR was always done more or less. Back in 2019 it was almost merged the evening before Play 2.8, Marcos almost wanted to merge but since they didn't want to include new features anymore they skipped it. Only Dale had a small concern that returning null isn't really nice. However, this is a thing we could improve in an upcoming PRs if people think that is necessary and wouldn't change this PR anyway. Since the default stays as it is and we can enhance things later this is neglectable at this point IMHO. I wanted to get this in personally a long time since I found it very useful for projects I worked on back then and I do not see a reason to hold this back any longer. During reviewing/finishing this PR I ran into three other things:
This is is. |
The idea is to store the needed logic (which depends on the different server backends) via a lambda into a request attribute and call it later when needed. Curious what you think about this (I hope I didn't overlook something and this is complete bs. But its working like a charm...)
For the Java API everything is done automatically. For the Scala API you need to call the
parseBody
method yourself, because we don't have so much control about the Scala actions like we have about the Java ones. (However this also gives Scala users more flexibility, meaning I can sell this as a feature 😉)Please have a look at the docs I added, I think it explains the motivation behind this pull request. Marcos and I discussed this as well already. I had real world use cases for such feature already, and it looks like others as well (found another one which I didn't save the link for.)
The relevant implementation actually only happens in
Action.scala
and in the two server backends.To help reviewing, here are the test results for the integration tests I added.
Fixes #9634