Skip to content
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

Alter Router.Routes to give PathPattern information #3378

Closed
mrpmorris opened this issue Sep 7, 2014 · 15 comments
Closed

Alter Router.Routes to give PathPattern information #3378

mrpmorris opened this issue Sep 7, 2014 · 15 comments

Comments

@mrpmorris
Copy link

In order for plugins to know more information about the incoming request could PlayFramework alter the Router.Routes trait so that it is possible to get the Method and PathPattern?

The intent is for the Global object to be able to use onRequestReceived to extract values from the request's URL

GET /x/:name/:age controllers.Application.tester(name: String, age: Int)

I could then get a list of parameters, their types, and their values as per the current request.

@jroper
Copy link
Member

jroper commented Sep 8, 2014

This is already provided, through the ROUTE_PATTERN tag that can be accessed from RequestHeader.tags.

@jroper jroper closed this as completed Sep 8, 2014
@mrpmorris
Copy link
Author

I'm not sure what you say is possible, perhaps I have not stated my requirement clearly enough?

I wanted to handle the request at the earliest opportunity (GlobalSettings.onRequestReceived) but the tags do not exist in the request until GlobalSettings.doFilter - but putting this aside, how is it possible to take the following information

ROUTE_PATTERN = /x/$name<[^/]+>/$age<[^/]+>

and deduce there is a name:String = "Pete" and an age: Int = 41 from the current requestHeader?

http://stackoverflow.com/questions/25699895/how-do-i-extract-the-value-of-a-route-variable-from-the-url-in-a-scala-play-app

@jroper
Copy link
Member

jroper commented Sep 9, 2014

onRequestReceived is the thing that does the routing and tags the request, so of course it's not going to have any of the routing information when it's first invoked, only after it's invoked.

val p = """\$([^<]+)<([^>]+)>""".r
override def onRequestReceived(request: RequestHeader) = {
  val (taggedRequest, handler) = super.onRequestReceived(request)
  val pattern = taggedRequest.tags("ROUTE_PATTERN")
  val paramNames = p.findAllMatchIn(pattern).map(m => m.group(1)).toList
  val pathRegex = ("^" + p.replaceAllIn(pattern, m => "(" + m.group(2) + ")") + "$").r
  val paramValues = pathRegex.findFirstMatchIn(request.path).get.subgroups  
  val params: Map[String, String] = paramNames.zip(paramValues).toMap
  // ^ your params map, will be Map("name" -> "Pete", "age" -> "41")
  (taggedRequest, handler)
}

@jroper
Copy link
Member

jroper commented Sep 9, 2014

That said, there are usually better, more typesafe ways to achieve whatever you're trying to achieve. If you depend on there being specific parameters in the URL, then a filter is not the right thing, because filters apply to all requests, whether they have those parameters or not. Rather, you should probably be using action composition or a custom action builder, like so:

case class MyAction(name: String, age: Int) extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    // Do your filtering here, you have access to both the name and age above
    block(request)
  }
}

def foo(name: String, age: Int) = MyAction(name, age) { request =>
  Ok("Hello world")
}

def bar(name: String, age: Int) = MyAction(name, age).async { request =>
  Future.successful(Ok("Hello world"))
}

@mrpmorris
Copy link
Author

Many thanks!

@mrpmorris
Copy link
Author

One more question though, how would I also determine the parameter types?

@jroper
Copy link
Member

jroper commented Sep 9, 2014

While possible, I don't think it's worth it. Do you need to know the parameter types? Or is it fine to just convert them as needed?

@mrpmorris
Copy link
Author

It's not imperative, but knowing the types of the parameters would allow for much more control.

One of the things I like about C# asp.mvc is that you can subclass a route and use your own for definitions, allowing you to add more information to the route itself (e.g. a coder-specified unique ID) rather than having to decorate actions on controllers, but as the routes file is a kind of DSL it seems this kind of thing wouldn't be possible, would it?

@jroper
Copy link
Member

jroper commented Sep 11, 2014

It's still quite hacky to look at a Map[String, Any], and it's not something that I've ever had to do, I've always found decorating actions on controllers to be much more powerful and easier/safer to maintain - change the type or name of a parameter, and the code fails to compile. In this, change something, everything breaks silently.

The thing about Play's routes is that they are intentionally limiting - for simplicity. We could allow you to do anything and everything, but then that's already allowed by simply implementing your own router that implements play.core.Router.Routes.

@mrpmorris
Copy link
Author

When wanting authorization on every page except a login page it is a lot of repetitive work to have to compose every action, and if writing a module it is too much to expect someone to have to use a custom router.

I was going to implement a security model I made in C# but it seems Play just isn't up to the job.

@jroper
Copy link
Member

jroper commented Sep 15, 2014

Is it really repetitive to compose every action? You define a custom action builder once, and then instead of every action using the built in action builder, you make every action use the custom action builder, eg:

def index = Action {
  Ok
}

becomes

def index = Authenticated {
  Ok
}

How is that repetitive? You seem to be set on a particular solution, rather than set on solving a problem. Of course the solution you implemented for one particular framework won't transfer 1:1 to another particular framework, because those are different frameworks. But that doesn't mean that the problem you want to solve can't be solved in both frameworks, it just means they have different solutions.

@mrpmorris
Copy link
Author

I'm not actually set on a particular solution at all, I am set on a specific requirement. I want this to be a generic module that can be used not only in multiple sites but also by people I have never met. If those people want to authorize all calls, and only log some (using another 3rd party module), and do something else too then they end up with lots of action composition. What I am looking for is some kind of interception approach, a cross cutting concern rather than something you explicitly declare on each action - Just like the Global.onRequestReceived hook (which is why I intend to use it).

Perhaps if there was some kind hook in the Router which allowed users to return a wrapper instance for "call"? I don't know what the answer is, but I am after some kind of interception. Having people use my custom router is too big a requirement for them to want to use the module.
untitled-1

@shezi
Copy link

shezi commented Oct 7, 2015

All of this is very nice and workable, but completely unavailable from Java code. Being able to access the path parameters from Java would make a whole class of Action compositions possible in Java.

Even the official documentation shows something of the like (but then ducks out of actually showing how to get the user parameter) in https://www.playframework.com/documentation/2.4.x/JavaActionsComposition#Passing-objects-from-action-to-controller

What exactly would be the drawback of carrying an array with all the URL parameters somewhere?

@shezi
Copy link

shezi commented Oct 7, 2015

One more note: It does not seem to be a huge amount of code considering that this can be implemented/hacked in about 30 lines when using Scala. As shown here: https://alots.wordpress.com/2014/05/01/accessing-url-parameters-as-get-parameters-in-play/

Also note that there are now quite a few indications that this would be a useful feature.

@kiamesdavies
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants