Skip to content

Commit 32e5f1a

Browse files
Merge pull request #40 from lichess-org/revert-handler
Revert handler
2 parents d210eee + 3293efb commit 32e5f1a

9 files changed

Lines changed: 148 additions & 43 deletions

File tree

core/play/src/main/scala/play/api/http/HttpRequestHandler.scala

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@ trait HttpRequestHandler:
2121
* will be tagged with routing information. It is also acceptable to simply return the request as is. Play
2222
* will switch to using the returned request from this point in in its request handling.
2323
*
24-
* The reason why the API allows returning a modified request, rather than just wrapping the action in a new
25-
* action that modifies the request, is so that Play can pass this request to other handlers, such as error
26-
* handlers, or filters, and they will get the tagged/modified request.
24+
* The reason why the API allows returning a modified request, rather than just wrapping the Handler in a
25+
* new Handler that modifies the request, is so that Play can pass this request to other handlers, such as
26+
* error handlers, or filters, and they will get the tagged/modified request.
2727
*
2828
* @param request
2929
* The request to handle
3030
* @return
3131
* The possibly modified/tagged request, and a handler to handle it
3232
*/
33-
def handlerForRequest(request: RequestHeader): (RequestHeader, EssentialAction)
33+
def handlerForRequest(request: RequestHeader): (RequestHeader, Handler)
3434

3535
/**
3636
* Implementation of a [HttpRequestHandler] that always returns NotImplemented results
@@ -96,7 +96,7 @@ class DefaultHttpRequestHandler(
9696
context.isEmpty ||
9797
(path.startsWith(context) && (path.length == context.length || path.charAt(context.length) == '/'))
9898

99-
override def handlerForRequest(request: RequestHeader): (RequestHeader, EssentialAction) =
99+
override def handlerForRequest(request: RequestHeader): (RequestHeader, Handler) =
100100
def handleWithStatus(status: Int) =
101101
ActionBuilder.ignoringBody.async(BodyParsers.utils.empty)(req =>
102102
errorHandler.onClientError(req, status)
@@ -107,17 +107,18 @@ class DefaultHttpRequestHandler(
107107
* isn't explicitly routed try routing it as a GET request. Second, if no routing information is present,
108108
* fall back to a 404 error.
109109
*/
110-
def routeWithFallback(request: RequestHeader): EssentialAction =
110+
def routeWithFallback(request: RequestHeader): Handler =
111111
routeRequest(request).getOrElse {
112112
request.method match
113113
// We automatically permit HEAD requests against any GETs without the need to
114114
// add an explicit mapping in Routes. Since we couldn't route the HEAD request,
115-
// try to get an action for the equivalent GET request instead. Notes:
115+
// try to get a Handler for the equivalent GET request instead. Notes:
116116
// 1. The handler returned will still be passed a HEAD request when it is
117117
// actually evaluated.
118118
case HttpVerbs.HEAD =>
119-
routeRequest(request.withMethod(HttpVerbs.GET)).getOrElse:
120-
handleWithStatus(NOT_FOUND)
119+
routeRequest(request.withMethod(HttpVerbs.GET)) match
120+
case Some(handler: Handler) => handler
121+
case None => handleWithStatus(NOT_FOUND)
121122
case _ =>
122123
// An Action for a 404 error
123124
handleWithStatus(NOT_FOUND)
@@ -128,16 +129,21 @@ class DefaultHttpRequestHandler(
128129
// 3. Modify the handler to do filtering, if necessary
129130
// 4. Again resolve any handlers that do preprocessing
130131
val routedHandler = routeWithFallback(request)
131-
(request, filterHandler(request, routedHandler))
132+
val (preprocessedRequest, preprocessedHandler) = Handler.applyStages(request, routedHandler)
133+
val filteredHandler = filterHandler(preprocessedRequest, preprocessedHandler)
134+
val (preprocessedPreprocessedRequest, preprocessedFilteredHandler) =
135+
Handler.applyStages(preprocessedRequest, filteredHandler)
136+
(preprocessedPreprocessedRequest, preprocessedFilteredHandler)
132137

133138
/**
134139
* Update the given handler so that when the handler is run any filters will also be run. The default
135140
* behavior is to wrap all [[play.api.mvc.EssentialAction]]s by calling `filterAction`, but to leave other
136141
* kinds of handlers unchanged.
137142
*/
138-
protected def filterHandler(request: RequestHeader, action: EssentialAction): EssentialAction =
139-
if inContext(request.path) then filterAction(action)
140-
else action
143+
protected def filterHandler(request: RequestHeader, handler: Handler): Handler =
144+
handler match
145+
case action: EssentialAction if inContext(request.path) => filterAction(action)
146+
case handler => handler
141147

142148
/**
143149
* Apply filters to the given action.
@@ -158,5 +164,5 @@ class DefaultHttpRequestHandler(
158164
* @return
159165
* A handler to handle the request, if one can be found
160166
*/
161-
def routeRequest(request: RequestHeader): Option[EssentialAction] =
167+
def routeRequest(request: RequestHeader): Option[Handler] =
162168
router().handlerFor(request)

core/play/src/main/scala/play/api/mvc/Action.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import scala.concurrent.*
1616
* An `EssentialAction` underlies every `Action`. Given a `RequestHeader`, an `EssentialAction` consumes the
1717
* request body (an `ByteString`) and returns a `Result`.
1818
*
19-
* An `EssentialAction` is the one that Play uses to handle requests.
19+
* An `EssentialAction` is a `Handler`, which means it is one of the objects that Play uses to handle
20+
* requests.
2021
*/
21-
trait EssentialAction extends (RequestHeader => Accumulator[ByteString, Result]):
22+
trait EssentialAction extends (RequestHeader => Accumulator[ByteString, Result]) with Handler:
2223
self =>
2324

2425
/** @return itself, for better support in the routes file. */
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
3+
*/
4+
5+
package play.api.mvc
6+
7+
import scala.annotation.tailrec
8+
9+
/**
10+
* An Handler handles a request. Play understands several types of handlers, for example `EssentialAction`s
11+
* and `WebSocket`s.
12+
*
13+
* The `Handler` used to handle the request is controlled by `GlobalSetting`s's `onRequestReceived` method.
14+
* The default implementation of `onRequestReceived` delegates to `onRouteRequest` which calls the default
15+
* `Router`.
16+
*/
17+
trait Handler
18+
19+
object Handler:
20+
21+
/**
22+
* Some handlers are built as a series of stages, with each stage returning a new [[RequestHeader]] and
23+
* another stage, until eventually a terminal handler is returned. This method processes all stages in a
24+
* handler, if any, returning a terminal handler such as `EssentialAction` or `WebSocket`.
25+
*
26+
* @param requestHeader
27+
* The current RequestHeader.
28+
* @param handler
29+
* The input Handler.
30+
* @return
31+
* The new RequestHeader and Handler.
32+
*/
33+
@tailrec
34+
def applyStages(requestHeader: RequestHeader, handler: Handler): (RequestHeader, Handler) = handler match
35+
case m: Stage =>
36+
// Call the ModifyRequest logic to get the new header and handler. The
37+
// new handler could have its own modifications to apply to the header
38+
// so we call `applyPreprocessingHandlers` recursively on the result.
39+
val (newRequestHeader, newHandler) = m.apply(requestHeader)
40+
applyStages(newRequestHeader, newHandler)
41+
case _ =>
42+
// This is a normal handler that doesn't do any preprocessing.
43+
(requestHeader, handler)
44+
45+
/**
46+
* A special type of [[play.api.mvc.Handler]] which allows custom logic to be inserted during handling. A
47+
* `Stage` accepts a `RequestHeader` then returns a new `RequestHeader` along with the next `Handler` to use
48+
* during request handling. The next handler could be a terminal `Handler` like an [[EssentialAction]], but
49+
* it could also be another `Stage`. This means it's possible to chains of `Stage`s that should each be
50+
* executed in turn. To automatically execute all `Stage`s you can call
51+
* [[play.api.mvc.Handler.applyStages]].
52+
*/
53+
trait Stage extends Handler:
54+
def apply(requestHeader: RequestHeader): (RequestHeader, Handler)
55+
56+
object Stage:
57+
58+
/**
59+
* Create a `Stage` that modifies the request before calling the next handler.
60+
*/
61+
def modifyRequest(
62+
modifyRequestFunc: RequestHeader => RequestHeader,
63+
wrappedHandler: Handler
64+
): Handler.Stage =
65+
(requestHeader: RequestHeader) => (modifyRequestFunc(requestHeader), wrappedHandler)

core/play/src/main/scala/play/api/routing/Router.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
package play.api.routing
66

77
import play.api.libs.typedmap.TypedKey
8+
import play.api.mvc.Handler
89
import play.api.mvc.RequestHeader
9-
import play.api.mvc.EssentialAction
1010
import play.api.routing.Router.Routes
1111

1212
/**
@@ -23,7 +23,7 @@ trait Router:
2323
/**
2424
* A lifted version of the routes partial function.
2525
*/
26-
final def handlerFor(request: RequestHeader): Option[EssentialAction] = routes.lift(request)
26+
final def handlerFor(request: RequestHeader): Option[Handler] = routes.lift(request)
2727

2828
/**
2929
* Compose two routers into one. The resulting router will contain both the routes in `this` as well as
@@ -40,7 +40,7 @@ object Router:
4040
/**
4141
* The type of the routes partial function
4242
*/
43-
type Routes = PartialFunction[RequestHeader, EssentialAction]
43+
type Routes = PartialFunction[RequestHeader, Handler]
4444

4545
/**
4646
* Request attributes used by the router.

core/play/src/main/scala/play/core/routing/GeneratedRouter.scala

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ object Route:
3232
* An included router
3333
*/
3434
class Include(val router: Router):
35-
def unapply(request: RequestHeader): Option[EssentialAction] =
35+
def unapply(request: RequestHeader): Option[Handler] =
3636
router.routes.lift(request)
3737

3838
/**
@@ -76,28 +76,27 @@ abstract class GeneratedRouter extends Router {
7676
errorHandler.onClientError(request, play.api.http.Status.BAD_REQUEST, error)
7777
}
7878

79-
def named(name: String)(generator: => EssentialAction): EssentialAction =
80-
EssentialAction: (request: RequestHeader) =>
81-
generator(request.addAttr(play.api.routing.Router.Attrs.ActionName, name))
79+
def named(name: String)(generator: => Handler) =
80+
Handler.Stage.modifyRequest(_.addAttr(play.api.routing.Router.Attrs.ActionName, name), generator)
8281

83-
def call(generator: => EssentialAction): EssentialAction =
82+
def call(generator: => Handler): Handler =
8483
generator
8584

86-
def call[P](pa: Param[P])(generator: (P) => EssentialAction): EssentialAction =
85+
def call[P](pa: Param[P])(generator: (P) => Handler): Handler =
8786
pa.value.fold(badRequest, generator)
8887

8988
// Keep the old versions for avoiding compiler failures while building for Scala 2.10,
9089
// and for avoiding warnings when building for newer Scala versions
9190
// format: off
92-
def call[A1, A2](pa1: Param[A1], pa2: Param[A2])(generator: Function2[A1, A2, EssentialAction]): EssentialAction = {
91+
def call[A1, A2](pa1: Param[A1], pa2: Param[A2])(generator: Function2[A1, A2, Handler]): Handler = {
9392
(for
9493
a1 <- pa1.value
9594
a2 <- pa2.value
9695
yield (a1, a2))
9796
.fold(badRequest, { case (a1, a2) => generator(a1, a2) })
9897
}
9998

100-
def call[A1, A2, A3](pa1: Param[A1], pa2: Param[A2], pa3: Param[A3])(generator: Function3[A1, A2, A3, EssentialAction]): EssentialAction = {
99+
def call[A1, A2, A3](pa1: Param[A1], pa2: Param[A2], pa3: Param[A3])(generator: Function3[A1, A2, A3, Handler]): Handler = {
101100
(for
102101
a1 <- pa1.value
103102
a2 <- pa2.value
@@ -106,7 +105,7 @@ a1 <- pa1.value
106105
.fold(badRequest, { case (a1, a2, a3) => generator(a1, a2, a3) })
107106
}
108107

109-
def call[A1, A2, A3, A4](pa1: Param[A1], pa2: Param[A2], pa3: Param[A3], pa4: Param[A4])(generator: Function4[A1, A2, A3, A4, EssentialAction]): EssentialAction = {
108+
def call[A1, A2, A3, A4](pa1: Param[A1], pa2: Param[A2], pa3: Param[A3], pa4: Param[A4])(generator: Function4[A1, A2, A3, A4, Handler]): Handler = {
110109
(for
111110
a1 <- pa1.value
112111
a2 <- pa2.value
@@ -116,7 +115,7 @@ a1 <- pa1.value
116115
.fold(badRequest, { case (a1, a2, a3, a4) => generator(a1, a2, a3, a4) })
117116
}
118117

119-
def call[A1, A2, A3, A4, A5](pa1: Param[A1], pa2: Param[A2], pa3: Param[A3], pa4: Param[A4], pa5: Param[A5])(generator: Function5[A1, A2, A3, A4, A5, EssentialAction]): EssentialAction = {
118+
def call[A1, A2, A3, A4, A5](pa1: Param[A1], pa2: Param[A2], pa3: Param[A3], pa4: Param[A4], pa5: Param[A5])(generator: Function5[A1, A2, A3, A4, A5, Handler]): Handler = {
120119
(for
121120
a1 <- pa1.value
122121
a2 <- pa2.value
@@ -127,7 +126,7 @@ a1 <- pa1.value
127126
.fold(badRequest, { case (a1, a2, a3, a4, a5) => generator(a1, a2, a3, a4, a5) })
128127
}
129128

130-
def call[A1, A2, A3, A4, A5, A6](pa1: Param[A1], pa2: Param[A2], pa3: Param[A3], pa4: Param[A4], pa5: Param[A5], pa6: Param[A6])(generator: Function6[A1, A2, A3, A4, A5, A6, EssentialAction]): EssentialAction = {
129+
def call[A1, A2, A3, A4, A5, A6](pa1: Param[A1], pa2: Param[A2], pa3: Param[A3], pa4: Param[A4], pa5: Param[A5], pa6: Param[A6])(generator: Function6[A1, A2, A3, A4, A5, A6, Handler]): Handler = {
131130
(for
132131
a1 <- pa1.value
133132
a2 <- pa2.value
@@ -139,9 +138,35 @@ a1 <- pa1.value
139138
.fold(badRequest, { case (a1, a2, a3, a4, a5, a6) => generator(a1, a2, a3, a4, a5, a6) })
140139
}
141140

141+
def call[A1, A2, A3, A4, A5, A6, A7](pa1: Param[A1], pa2: Param[A2], pa3: Param[A3], pa4: Param[A4], pa5: Param[A5], pa6: Param[A6], pa7: Param[A7])(generator: Function7[A1, A2, A3, A4, A5, A6, A7, Handler]): Handler = {
142+
(for
143+
a1 <- pa1.value
144+
a2 <- pa2.value
145+
a3 <- pa3.value
146+
a4 <- pa4.value
147+
a5 <- pa5.value
148+
a6 <- pa6.value
149+
a7 <- pa7.value
150+
yield (a1, a2, a3, a4, a5, a6, a7))
151+
.fold(badRequest, { case (a1, a2, a3, a4, a5, a6, a7) => generator(a1, a2, a3, a4, a5, a6, a7) })
152+
}
153+
154+
def call[A1, A2, A3, A4, A5, A6, A7, A8](pa1: Param[A1], pa2: Param[A2], pa3: Param[A3], pa4: Param[A4], pa5: Param[A5], pa6: Param[A6], pa7: Param[A7], pa8: Param[A8])(generator: Function8[A1, A2, A3, A4, A5, A6, A7, A8, Handler]): Handler = {
155+
(for
156+
a1 <- pa1.value
157+
a2 <- pa2.value
158+
a3 <- pa3.value
159+
a4 <- pa4.value
160+
a5 <- pa5.value
161+
a6 <- pa6.value
162+
a7 <- pa7.value
163+
a8 <- pa8.value
164+
yield (a1, a2, a3, a4, a5, a6, a7, a8))
165+
.fold(badRequest, { case (a1, a2, a3, a4, a5, a6, a7, a8) => generator(a1, a2, a3, a4, a5, a6, a7, a8) })
166+
}
142167
// format: on
143168

144-
def call[T](params: List[Param[?]])(generator: (Seq[?]) => EssentialAction): EssentialAction =
169+
def call[T](params: List[Param[?]])(generator: (Seq[?]) => Handler): Handler =
145170
(params
146171
.foldLeft[Either[String, Seq[?]]](Right(Seq[T]())) { (seq, param) =>
147172
seq.flatMap(s => param.value.map(s :+ _))

dev-mode/routes-compiler/src/main/twirl/play/routes/compiler/inject/forwardsRouter.scala.twirl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ case include @ Include(path, router) => {
3535
private val prefixed_@(dep.ident)_@(pathIndex * 1000 + index) = Include(@(dep.ident))
3636
}}}}
3737

38-
val pathRouters: Map[String, PartialFunction[RequestHeader, EssentialAction]] = Map(
38+
val pathRouters: Map[String, PartialFunction[RequestHeader, Handler]] = Map(
3939
@for(((path, rules), pathIndex) <- pathRules.zipWithIndex) {
4040
"@path" -> @ob
4141
@for((dep, index) <- rules.zipWithIndex){@dep.rule match {
@@ -57,7 +57,7 @@ case include @ Include(path, router) => {
5757
}
5858
)
5959

60-
val routes: PartialFunction[RequestHeader, EssentialAction] =
60+
val routes: PartialFunction[RequestHeader, Handler] =
6161
@if(pathRules.isEmpty) { Map.empty } else {@ob
6262
val emptyPathRouter = pathRouters get ""
6363
Function unlift @ob (req: RequestHeader) =>

dev-mode/routes-compiler/src/test/resources/snapshot/routes/lila-generated.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,7 +1078,7 @@ final class Routes(
10781078
)
10791079

10801080

1081-
val pathRouters: Map[String, PartialFunction[RequestHeader, EssentialAction]] = Map(
1081+
val pathRouters: Map[String, PartialFunction[RequestHeader, Handler]] = Map(
10821082

10831083
"" -> {
10841084

@@ -2774,7 +2774,7 @@ final class Routes(
27742774

27752775
)
27762776

2777-
val routes: PartialFunction[RequestHeader, EssentialAction] =
2777+
val routes: PartialFunction[RequestHeader, Handler] =
27782778
{
27792779
val emptyPathRouter = pathRouters get ""
27802780
Function unlift { (req: RequestHeader) =>

transport/server/play-netty-server/src/main/scala/play/core/server/netty/PlayRequestHandler.scala

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ private[play] class PlayRequestHandler(
5757

5858
val tryRequest: Try[RequestHeader] = modelConversion.convertRequest(channel, request)
5959

60-
def clientError(statusCode: Int, message: String): (RequestHeader, EssentialAction) =
60+
def clientError(statusCode: Int, message: String): (RequestHeader, Handler) =
6161
val unparsedTarget = modelConversion.createUnparsedRequestTarget(request)
6262
val requestHeader = modelConversion.createRequestHeader(channel, request, unparsedTarget)
6363
val result = app.errorHandler.onClientError(
@@ -68,7 +68,7 @@ private[play] class PlayRequestHandler(
6868
// If there's a problem in parsing the request, then we should close the connection, once done with it
6969
requestHeader -> Server.actionForResult(result.map(_.withHeaders(HeaderNames.CONNECTION -> "close")))
7070

71-
val (requestHeader, handler): (RequestHeader, EssentialAction) = tryRequest match
71+
val (requestHeader, handler): (RequestHeader, Handler) = tryRequest match
7272
case Failure(exception: TooLongFrameException) =>
7373
clientError(Status.REQUEST_URI_TOO_LONG, exception.getMessage)
7474
case Failure(exception) => clientError(Status.BAD_REQUEST, exception.getMessage)
@@ -80,7 +80,15 @@ private[play] class PlayRequestHandler(
8080
then clientError(Status.REQUEST_ENTITY_TOO_LARGE, "Request Entity Too Large")
8181
else Server.getHandlerFor(req, app)
8282

83-
handleAction(handler, requestHeader, request)
83+
handler match
84+
// execute normal action
85+
case action: EssentialAction => handleAction(action, requestHeader, request)
86+
87+
// This case usually indicates an error in Play's internal routing or handling logic
88+
case h =>
89+
val ex = new IllegalStateException(s"Netty server doesn't handle Handlers of this type: $h")
90+
logger.error(ex.getMessage, ex)
91+
throw ex
8492

8593
// ----------------------------------------------------------------
8694
// Netty overrides
@@ -169,7 +177,7 @@ private[play] class PlayRequestHandler(
169177
requestHeader: RequestHeader,
170178
request: HttpRequest
171179
): Future[HttpResponse] =
172-
given mat: Materializer = app.materializer
180+
implicit val mat: Materializer = app.materializer
173181
import play.core.Execution.Implicits.trampoline
174182

175183
// Execute the action on the Play default execution context

0 commit comments

Comments
 (0)