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.
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.
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&address=john#doe.com
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.
Related
I am trying to make some conditional routes. The condition resolves on the serverside.
Route rule example:
| (dynamicRouteCT("#user" / long.caseClass[User]) ~> dynRender((page: User) => <.div("Hello, " + page.id.toString)))
.addCondition((page: User) => checkPermissions(page.id))(_ => Some(redirectToPage(Page403)(Redirect.Push)))
checkpermissions body:
def checkPermissions(id: Long) = CallbackTo.future{
/*Ajax.get(s"http://some.uri/?id=$id") map (res =>
* if (something) true
* else false
* )
*/
//the request before returns Future[XMLHttprequest] witch maps to Future[Boolean]
Future(false)
}
I got type missmatch here: (page: User) => checkPermissions(page.id)
Is it possible to perform ajax request inside conditional routes?
If we look at def addCondition(cond: Page => CallbackTo[Boolean])(condUnmet: Page => Option[Action[Page]]): Rule[Page] we can see that it requires a CallbackTo[Boolean]. Because of the nature of the JS env, there is now way to go from Future[A] to A. Although it's not a limitation from scalajs-react itself, it is an inherited reality that will affect your scalajs-react code; as this table in the doc shows, there's no way to go from a CallbackTo[Future[Boolean]] to a CallbackTo[Boolean].
This type-level restriction is actually a really good thing for user experience. The router is synchronous, it must determine how to render routes and route changes immediately. If it were allowed to be async and somehow supported Futures, then the user would experience noticable (and potentially huge) delays without any kind of visual feedback or means of interruption.
The "right way" to solve this problem is to use a model that covers the async state. This is what I would do:
Create an AsyncState[E, A] ADT with cases: Empty, AwaitingResponse, Loaded(value: A), Failed(error: E).(You can enrich these further if desired, eg. loadTime on Loaded, retry callback on Failed, timeStarted on AwaitingResponse, etc.)
Have an instance of AsyncState[Boolean] in your (local/client-side) state.
Optionally kick-off an async load on page startup.
Have the router pass its value to a component and/or check the value of this.(The router won't know the value because it's dynamic, use Callback in a for-comprehension to wire things up and satisfy the types.)
Depending on the value of AsyncState[Boolean], render something meaningful to the user. If it's AwaitingResponse, display a little spinner; if it's failed display an error and probably a retry button.
(It should also be noted that AsyncState[Boolean] shouldn't actually be Boolean as that's not very descriptive or true to the domain. It would probably be something more meaningful like AsyncState[UserAccess] or something like that.)
Hope that helps! Good luck!
I'm developing a scala application with play frame work, i have created a filter that filters every request coming from outside server,but now i'm stuck on how can i run a filter on demand since two days,i have 80 APIs 30 of them needs to run a specific filter, how can i read the request route template while the requests like this
GET /api/v1/:locale/:uuid core.controllers.MyClass.myAction1(locale: String)
GET /api/v1/:locale/:uuid/MyRoute core.controllers.MyClass.myAction2(locale: String)
GET /api/v1/:locale/:uuid/Foo core.controllers.MyClass.myAction3(locale: String)
GET /api/v1/:locale/orders/:orderId core.controllers.MyClass.myAction4(locale: String)
well, those routes are placed in routes file,
in filter i need to check weather if the route has :uuid variable or :orderId in order to run its specific filter, because both of their ids, i getting them as uuid so i couldn't expect the request, could i read the route template ?
You can access to some routing information from the RequestHeader#attrs:
// in your filter
val handlerDef: Option[HandlerDef] = request.attrs.get(Router.Attrs.HandlerDef)
See HandlerDef api
If you want to choose 30 out of 80 actions to run some common logic, you could also consider using "action builders" to provide that logic.
When you use Action { ... } you get a vanilla action. You can also make your own MyAction { ... } that wraps a normal Action and runs custom logic. This is an ActionBuilder. If you use this approach you just need to update your 30 actions to use that custom action builder.
See: https://www.playframework.com/documentation/2.6.x/ScalaActionsComposition#Custom-action-builders
Say I have the following url:
/baseurl
I'd like play to match and route on this url. I'd like to satisfy all these patterns:
/baseurl
/baseurl/
/baseurl/*
/baseurl/*/*
So basically I don't care what comes after '/baseurl'. I don't want to explicitly have to pass a variable to my action for the part coming in after '/baseurl' because I don't care about it: I just want it all routed to the same controller (for a single page app). I also am ok if I have to do this with multiple route lines.
I'd put something like this pretty high in the routes file:
GET /baseurl/*path/ controllers.Application.untrail(path: String)
(and the variations thereof)
And then in controllers.Application:
def untrail(path: String) = Action {
MovedPermanently("/baseurl")
}
Working on a project and using Spray
Want to add Swagger to get a nice UI for all the calls
Found :
http://github.com/gettyimages/spray-swagger
Problem is I cant seem to get it to work with my project, with no docs or
run examples its like walking in the dark .... (and time consuming)
did any 1 get to work with this and has a
test example
wiki page
any thing helpful
So i can get get it working?
Thanks!
So I took a stab at this and got it working. You can check out my repo here.
Granted, it is my scratchpad for exploring Spray, so some things may change. Also, this is based on Spray 1.3.0.
The general gist of getting this to work, as I understand it, is:
You have your traits/classes that hold your routes; lets call this a "Resource". Each one of these traits/classes roughly corresponds to a "path" (e.g. /posts)
In your Resources, instead of directly using the Spray DSL to construct one big route, you need to have one method for each endpoint(roughly corresponding to an "action" in Rails controller, or a route in Sinatra/Scalatra). These methods need to be annotated. So for example you would have separate def postCreate, def postDeletes, etc. These would then need to be composed separately for your Resource.
You instantiate a SwaggerHttpService, implementing the necessary methods (including a Sequence of all the traits/classes in 1, and a Sequence of all your ApiModels that are used in your annotations).
The combination of all your routes (across your actual API and the SwaggerHttpService) needs to get passed to your central routing actor's receive method wrapped in a runRoute (this is the actor that gets in Boot.scala)
I believe 1. and 2. are necessary because spray-swagger works with Java annotations, which cannot be retrieved when wrapped inside an opaque function (which is what the Spray routing DSL normally composes for you).
the project has been updated with a wiki and usage information!
This looks promising :
https://github.com/gettyimages/spray-swagger
didn't try it out yet but looking good!
One of my concerns spray-swagger is that the annotations make the code harder to read. Any ideas for how to make the code readable & still get the benefits of Swagger?
#Api(value = "/pet", description = "Operations about pets")
trait PetHttpService extends HttpService {
#ApiOperation(httpMethod = "GET", response = classOf[Pet], value = "Returns a pet based on ID")
#ApiImplicitParams(Array(
new ApiImplicitParam(name = "petId", required = false, dataType = "integer", paramType = "path", value = "ID of pet that needs to be fetched")
))
#ApiResponses(Array(
new ApiResponse(code = 400, message = "Invalid ID Supplied"),
new ApiResponse(code = 404, message = "Pet not found")))
def petGetRoute = get { path("pet" / IntNumber) { petId =>
complete(s"Hello, I'm pet ${petId}!")
} }
}
I'm going to create a trait with annotations & extend to see if the annotations work. Will let you guys know what happens!
Action create shows form:
def create = Action {
Ok(html.post.create(postForm))
}
How can i modify this action so that for GET request it would give out form and for the POST request it would process user input data, as if it were a separate action:
def newPost = Action { implicit request =>
postForm.bindFromRequest.fold(
errors => BadRequest(views.html.index(Posts.all(), errors)),
label => {
Posts.create(label)
Redirect(routes.Application.posts)
}
)
}
Wthat i mean is i want to combine this two actions.
UPDATE1: I want a single Action that serves GET and POST requests
It is recommended not to merge both actions, but modify routes to get the behavior you are expecting. For instance:
GET /create controllers.Posts.create
POST /create controllers.Posts.newPost
In case you have several kind of resources (post and comments, for instance), just add
a prefix to the path to disambiguate:
GET /post/create controllers.Posts.create
POST /post/create controllers.Posts.newPost
GET /comment/create controllers.Comments.create
POST /comment/create controllers.Comments.newComment
I tried once to accomplish similar thing, but I realized that I wasn't using framework like it was meant to be used. Use separate GET and POST methods like #paradigmatic showed and in cases like you specified "If we take adding comments to another action, we wouldn't be able to get infomation on post and comments in case an error occured (avoding copy-paste code)." - just render the page at the end of controller method with the view you like? and for errors etc. you can always use flash scope too? http://www.playframework.org/documentation/2.0.2/ScalaSessionFlash you could also render this form page with two or more beans and send them to controller side to catch related error messages and data.?