Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Routing based on query parameter in Play framework

My web application will be triggered from an external system. It will call one request path of my app, but uses different query parameters for different kinds of requests.

One of the parameters is the "action" that defines what is to be done. The rest of the params depend on the "action".

So I can get request params like these:

action=sayHello&user=Joe
action=newUser&name=Joe&address=xxx
action=resetPassword
...

I would like to be able to encode it similarly in the routes file for play so it does the query param based routing and as much of the validation of other parameters as possible.

What I have instead is one routing for all of these possibilities with plenty of optional parameters. The action processing it starts with a big pattern match to do dispatch and parameter validation.

Googling and checking SO just popped up plenty of samples where the params are encoded in the request path somehow, so multiple paths are routed to the same action, but I would like the opposite: one path routed to different actions.

One of my colleagues said we could have one "dispatcher" action that would just redirect based on the "action" parameter. It would be a bit more structured then the current solution, but it would not eliminate the long list of optional parameters which should be selectively passed to the next action, so I hope one knows an even better solution :-)

BTW the external system that calls my app is developed by another company and I have no influence on this design, so it's not an option to change the way how my app is triggered.

like image 996
Sandor Murakozi Avatar asked Mar 28 '13 14:03

Sandor Murakozi


People also ask

In which folder does play expect routes file?

The answer to this question lies in the app/conf/routes configuration file. Play's router translates HTTP requests into action calls.

How do you use Javascript routing?

function route (path, template) { if (typeof template === 'function') { return routes[path] = template; } else if (typeof template === 'string') { return routes[path] = templates[template]; } else { return; }; };

Which play component is in charge of mapping incoming request URL into action?

The router is the component in charge of translating incoming HTTP Requests into action calls (a static, public method of a Controller).

What is routes Scala?

The routes file syntax conf/routes is the configuration file used by the router. This file lists all of the routes needed by the application. Each route consists of an HTTP method and URI pattern, both associated with a call to an Action generator.


2 Answers

The single dispatcher action is probably the way to go, and you don't need to specify all of your optional parameters in the route. If action is always there then that's the only one you really need.

GET  /someRoute      controller.dispatcher(action: String)

Then in your action method you can access request.queryString to get any of the other optional parameters.

like image 120
estmatic Avatar answered Sep 28 '22 23:09

estmatic


Note: I am NOT experienced Scala developer, so maybe presented snippets can be optimized... What's important for you they are valid and working.

So...

You don't need to declare every optional param in the routes file. It is great shortcut for type param's validation and best choice would be convince 'other company' to use API prepared by you... Anyway if you haven't such possibility you can also handle their requests as required.

In general: the dispatcher approach seems to be right in this place, fortunately you don't need to declare all optional params in the routes and pass it between actions/methods as they can be fetched directly from request. In PHP it can be compared to $_GET['action'] and in Java version of Play 2 controller - DynamicForm class - form().bindFromRequest.get("action").

Let's say that you have a route:

GET     /dispatcher      controllers.Application.dispatcher

In that case your dispatcher action (and additional methods) can look like:

def dispatcher = Action { implicit request =>
    request.queryString.get("action").flatMap(_.headOption).getOrElse("invalid") match {
      case "sayHello" => sayHelloMethod
      case "newUser"  => newUserMethod
      case _          => BadRequest("Action not allowed!")
    }
}

// http://localhost:9000/dispatcher?action=sayHello&name=John
def sayHelloMethod(implicit request: RequestHeader) = {
  val name = request.queryString.get("name").flatMap(_.headOption).getOrElse("")
  Ok("Hello " + name )
}

// http://localhost:9000/dispatcher?action=newUser&name=John+Doe&[email protected]
def newUserMethod(implicit request: RequestHeader) = {
  val name = request.queryString.get("name").flatMap(_.headOption).getOrElse("")
  val address = request.queryString.get("address").flatMap(_.headOption).getOrElse("")
  Ok("We are creating new user " + name + " with address " + address)
}

Of course you will need to validate incoming types and values 'manually', especially when actions will be operating on the DataBase, anyway biggest part of your problem you have resolved now.

like image 38
biesior Avatar answered Sep 28 '22 21:09

biesior