Skip to content

Instantly share code, notes, and snippets.

@brikis98
Last active December 17, 2015 08:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brikis98/5582159 to your computer and use it in GitHub Desktop.
Save brikis98/5582159 to your computer and use it in GitHub Desktop.
object RedirectFilter extends Filter {
def apply(next: RequestHeader => Result)(request: RequestHeader): Result = {
getRedirectParams(request).flatMap { case (newRequest, body) =>
route(newRequest).map { iteratee =>
AsyncResult(Enumerator(body).run(iteratee))
}
} getOrElse {
next(request)
}
}
private def getRedirectParams(request: RequestHeader): Option[(RequestHeader, Array[Byte])] = {
if (someCondition) {
Some(toPostRequest(request), "Some new body data".getBytes("UTF-8"))
} else {
None
}
}
private def toPostRequest(originalRequest: RequestHeader): RequestHeader = {
originalRequest.copy(
uri = "/some/new/uri",
path = "/some/new/uri",
method = "POST"
)
}
private def route(request: RequestHeader): Option[Iteratee[Array[Byte], Result]] = {
Play.global.onRouteRequest(request).collect {
case action: EssentialAction => Play.global.doFilter(action)(request)
}
}
}
@guillaumebort
Copy link

It depends if you are trying to build a generic solution or not.

If you are looking for something that would work with any Action and Body types: route(newRequest) give you the Action you want to redirect to. Actually you get the Action Iteratee. Just feed it with some binary data that will simulate the new request body, and as a result you will get the result.

If you know that the Action you are looking for is something well defined, like an Action[JsValue], you can pattern match it using the concrete class and apply it directly with a Request[JsValue]. You will get the result directly that you can then wrap into a Done Iteratee.

http://www.playframework.com/documentation/api/2.1.1/scala/index.html#play.api.libs.iteratee.Done$

@brikis98
Copy link
Author

@guillaumebort I updated the gist. It seems to work, but as far as I can tell, onRouteRequest just returns the Action used for that request and not any of the surrounding logic that Play adds around it. For example, I had to call doFilter myself, which feels wrong. I don't know if request tags will be set and other things that the framework should take care of.

Is there any way to hand this request to the play framework so that it behaves more or less identically to a brand new POST request that came from the browser? Browsing the Play code, I want the equivalent of calling PlayDefaultUpstreamHandler.messageReceived.

@guillaumebort
Copy link

@brikis98 Yes I see, you are right, there is 2 pieces of logic (concerning the Action invocation) applied in the Netty handler directly. For now I don't see how you could solve that. The only way id to do it yourself.

To be sure that the requestHeader is properly tagged, I think you need something like:

Play.global.onRouteRequest(request).collect {
  case action: EssentialAction => Play.global.doFilter(action)(request match {
    case r: RequestTaggingHandler => r.tagRequest(request)
    case _ => request
  })
}

It would probably be better if we could extract this "Action invocation steps" outside of the Netty handler to avoid the repetition and to make it available for plugins and tests. It seems easy, but perhaps there is additional problems I don't see here. You should bring this discussion to the play-framework-dev Google group.

@guillaumebort
Copy link

@brikis98 Yes I see, you are right, there is 2 pieces of logic (concerning the Action invocation) applied in the Netty handler directly. For now I don't see how you could solve that. The only way id to do it yourself.

To be sure that the requestHeader is properly tagged, I think you need something like:

Play.global.onRouteRequest(request).collect {
  case action: EssentialAction => Play.global.doFilter(action)(request match {
    case r: RequestTaggingHandler => r.tagRequest(request)
    case _ => request
  })
}

It would probably be better if we could extract this "Action invocation steps" outside of the Netty handler to avoid the repetition and to make it available for plugins and tests. It seems easy, but perhaps there is additional problems I don't see here. You should bring this discussion to the play-framework-dev Google group.

@jroper
Copy link

jroper commented May 22, 2013

Ok, so you want to supply a new body? If you don't want to supply a new body, then the easiest way is to do it in onRouteRequest. But if you want to supply a new body, you could do this in onRouteRequest too, but as a filter, you should use EssentialFilter:

object RedirectFilter extends EssentialFilter {
  def apply(next: EssentialAction) = new EssentialAction {
    def apply(rh: RequestHeader) = {
      getRedirectParams(rh).flatMap { case (newRequest, body) =>
        route(newRequest).map { iteratee =>
          // Ignore the actual body - or you could consume it and transform it somehow.
          Iteratee.ignore[Array[Byte]].mapM { _ =>
            // Feed in the new body to the new action
            Enumerator(body) |>>> iteratee
          }
        }
      } getOrElse {
        next(request)
      }
    }
  }

  // Other methods go here, and like Guillaume pointed out, make sure you tag it
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment