Play Framework Multi-Tenant Filter

2019-04-01 02:06发布

问题:

I'm attempting to build a multi-tenant application using Play Framework 2.2 and have run into a problem. I want to set a session key in the global onRouteRequest (or onRequest in Java) that identifies the site ID for the domain the user is requesting. In literally dozens of other frameworks this type of thing is painless (e.g. Django), but I'm learning that the session object in Play is apparently immutable, however.

So, right now, I have something like this:

override def onRouteRequest(request: RequestHeader): Option[Handler] = {
    if (request.session.get("site").isEmpty){   
      val id = models.Site.getSiteUIDFromURL(request.host.toLowerCase()).toString()
      if (!id.isEmpty){
       //what goes here to set the session?
      }else{
        //not found - redirect to a general notFound page
      }
    }
    super.onRouteRequest(request)
  }

And, although it's not the most efficient way using a database lookup, it works for testing right now. I need to be able to set a session key in the global but am completely lost on how to do that. If there are any better methods I am all ears (perhaps wrapping my controllers?).

I'm open to solution examples in either Java or Scala.

回答1:

Think of actions in Play as being function calls, the input is the request, the output is the result. If you want to change the result of a wrapped function call, then you must first invoke the function, and then apply your change. Adding a key to a session is changing the result, since the session is sent to the client in the session cookie. In the code above, you're trying to do the change before you have a result to change, ie, before you call super.onRouteRequest.

If you don't need to modify routing at all, then don't do this in onRouteRequest, do it in a filter, much easier there. But assuming you do need to modify routing, then you need to apply a filter to handler returned. This is what it might look like:

override def onRouteRequest(request: RequestHeader): Option[Handler] = {
  val maybeSite: Option[String] = request.session.get("site").orElse {
    // Let's just assume that getSiteUIDFromUrl returns Option[String], always use Option if you're returning values that might not exist.
    models.Site.getSiteUIDFromURL(request.host.toLowerCase())
  }

  maybeSite.flatMap { site =>
    super.onRouteRequest(request).map {
      case e: EssentialAction => EssentialAction { req =>
        e(req).map(_.withSession("site" -> site))
      }
      case other => other
    }
  }
}

Check the source code for the CSRFFilter to see examples of how to add things to the session in a filter.