How can I redirect to an error page in my Play app? - scala

Or more precisely...
I already have an error page route defined like so:
GET /error controllers.pages.ErrorController.page(msg: String, returnTo: String)
And a controller method like this:
object ErrorController extends Controller {
def page(msg: String, returnTo: String) = ReceiverRestricted { implicit req =>
val action = List(Button(F8, "Continue", Call("GET", returnTo)))
Results.Ok(views.html.base(Html("Oops"), List(Html(msg)), None, action))
}
}
If I programmatically call, say, ErrorController.page("You did something daft!", "/home") I get to a page that looks like I want, ie:
Oops
You did something daft!
F8 Continue
However the url is ugly:
http://localhost:9000/error?msg=You%20did%20something%20daft!&returnTo=/home
I want to change this so the msg= query parameter doesn't appear in the url. How can I accomplish this? I tried removing the query parameter and redirecting to the error page with the message passed in via the flash cookie - that worked but reloading the browser page loses the message. I can't use the session cookie because I already store other data in the session almost upto its limit.

You can use flash feature.
Here is a sample:
In your controller you can redirect the user to error page with:
Redirect("/error").flashing(
"reason" -> "The item has been created"
)
And in Error action:
def error = Action { implicit request =>
Ok {
val reason = flash.get("reason").getOrElse("General Error")
//DO your stuff with reason variable
}
}
Obviously you can have as many as flash variables you want.

Since Play is restful and stateless, I can't see an easy way to pass on an error message during a redirect without using Play's flash. Of course you could store the message in an temporary cookie in the browser. Another possibility could be to store it in your database (or whatever persistence technology you use), but this seems to be like cracking a nut with a sledgehammer.

Related

playframework 2.x form redirect with prefill

I'm trying to implement a user login form, I want to achieve:
when username doesn't exist in DB, return a flash message with previous
form data prefilled.
if any server side validation errors happened, return back to previous page, with old data pre-filled, display the errors
messages along side.
The problem now is, if I use flash scope, I need to use Redirect after post, but this will lose the pre-filled data.
If I use any status other than Redirect, I can't put data into flash scope.
What did I missing?
Don't use a Redirect for a failed login. You can return the same Form back to the login view with extra errors attached to it.
Something like this:
loginForm.bindFromRequest.fold(
formWithErrors => views.html.login(formWithErrors),
credentials => {
if(authenticate(credentials)) // dummy implementation
Redirect(controllers.Application.index)
else
BadRequest(views.html.login(loginForm.fill(credentials).withGlobalError("Incorrect login credentials")))
}
)
Then your view signature would look something like this:
#(loginForm: Form[Credentials])
#* Displays the first global error from the form, if any. *#
#loginForm.globalError.map{error =>
<h3>#error.message</h3>
}
And you'd pre-fill the form with values as before (I hope).
If there are multiple global errors, you can access them with globalErrors as it will access a Seq[FormError] instead of Option[FormError].
You can also attach errors to specific Form keys.
loginForm.withError("email", "I don't like your email.")
And would access them similarly:
#loginForm.error("email").map{ error =>
#error.message
}
You are using wrong concept, take a look to the Handling form submission doc, section: Validating a form in an Action
If form contains errors you are returning BadRequest (not Redirect)
If form IS valid anyway next check (i.e. DB query) returns an error you should do exactly the same thing as in formWithErrors so render the view passing incoming form to it (userData)
Finally if everything's OK, you can make your operation and redirect the user i.e. to main page or something...
Pseudo code (basing on doc):
def userPost = Action { implicit request =>
userForm.bindFromRequest.fold(
formWithErrors => {
BadRequest(views.html.user(formWithErrors))
},
userData => {
// Check whatever you need...
if (afterCheckSomethingIsWrong){
// if something's wrong fill the `userForm` with `userData` and render the same view again...
// You can use flash scope here i.e. for placing error message
BadRequest(views.html.user(userForm.fill(userData))).flashing("error" -> "The account doesn't exist")
} else {
// if everything is OK, redirect to some page, outside the form handling process, i.e. main page
Redirect(routes.Application.index).flashing("success" -> "Fine you're logged now")
}
}
)
}

Play framework make http request from play server to "somesite.com" and send the response back to the browser

I'm developing an application using Play framework in scala. I have to handle the below use case in my application.
For a particular request from the browser to the play server the Play server should make an http request to some external server (for Eg: somesite.com) and send the response from this request back to the web browser.
I have written the below code to send the request to external serever in the controller.
val holder = WS.url("http://somesite.com")
val futureResponse = holder.get
Now how do I send back the response recieved from "somesite.com" back to the browser?
There's an example in the Play documentation for WS, under Using in a controller; I've adapted it to your scenario:
def showSomeSiteContent = Action.async {
WS.url("http://somesite.com").get().map { response =>
Ok(response.body)
}
}
The key thing to note is the idiomatic use of map() on the Future that you get back from the get call - code inside this map block will be executed once the Future has completed successfully.
The Action.async "wrapper" tells the Play framework that you'll be returning a Future[Response] and that you want it to do the necessary waiting for things to happen, as explained in the Handling Asynchronous Results documentation.
You may also be interested in dynamically returning the status and content type:
def showSomeSiteContent = Action.async {
WS.url("http://somesite.com").get().map { response =>
Status(response.status)(response.body).as(response.ahcResponse.getContentType)
}
}
Dynamic status could help if the URL/service you call fails to answer correctly.
Dynamic content type can be handy if your URL/service can return different content HTML/XML... depending on some dynamic parameter.

Redirect to referer after a POST Request

I have a web application in Play. The web application consists of several pages. In every page there is a small flag that enables the user to change the language (locale) from german to english and back.
I handle this with a redirect to referer:
def referer(implicit request: Request[AnyContent]) =
request.headers.get(REFERER).getOrElse(mainUrl)
def locale(l: String) = Authenticated { user =>
implicit request =>
Redirect(referer).withCookies(Cookie(LANG, if (l == "de" || l == "en") l else "de"))
}
It is working fine. Well, at least for GET requests.
I have a specific page where the user has to input data in a form. This form is then POSTed to the server. Were errors found, the form is displayed again with the error messages, as usual. Now, if the user wants to change the language (by clicking on the flag), the redirect to referer does not work, because it tries to use a GET request, and Play complains that a GET route does not exist for this method (which is true).
I am solving this by caching the form and defining another method where the form is taken from the cache:
# User data is POSTed to the server
POST /create/insert controllers.MyCreate.insert()
# After a redirect the cached form is displayed again
GET /create/insert controllers.MyCreate.insertGet()
It works, but I don't like this solution. It does not seem normal to have to create another entry in the routes and another method just to adress this problem. I would need to add this hack for every POST route in my application!
Is there a more elegant solution to this?
You could change it into something like this (untested):
def changeLang(lang:String, returnUri:String) = Action {
Redirect(returnUri)
.withCookies(Cookie(LANG, if (lang == "de" || lang == "en") lang else "de"))
}
In you template you would output the route to changeLang in the link, you can get the uri via the request
#routes.Application.changeLang("en", request.uri).url
I suggest you make request implicit in your action and define it as implicit in your template so you don't need to pass it on to each template.
// in the controller
def myUrl = Action { implicit request =>
Ok(views.html.myTemplate("something"))
}
// in the template
#(title:String)(implicit request:play.api.mvc.RequestHeader)
Edit
As for the POST requests, it common (for these types of framework) to have POST requests simple handle stuff and then redirect to another page. The usual flow is like this:
Form submits to a handler
Handler does something with the form information
Handler redirects to a page
An example:
// Hooked up to a GET route
def edit(id:Long) = Action {
// render the view with a form that displays the element with given id
// if the flash scope contains validation information, use that in display
}
// Hooked up to a POST route
def editHandler = Action {
// validate the form
// if validation succeeds
// persist the object
// redirect to edit
// else
// put the form information into the flash scope
// put any validation messages into the flash scope
// redirect to edit
}
If you do not want to use this flow you need to have both a GET and POST route anyway. The user might do a page reload on the resulting page.

GET and POST request in one action. Playframework. Scala

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.?

Scalatra - how do we do an internal redirect / forward of request

I want to call another internal url from my scalatra 'controller'. I can't do a simple redirect, as there's some security settings that mean a user has access only to the first url.
Is there a way to do this?
get("/foo") {
servletContext.getRequestDispatcher("/bar").forward(request, response)
}
The get() method is defined as (similar to POST, et al):
def get(transformers : org.scalatra.RouteTransformer*)(action : => scala.Any) : org.scalatra.Route
Depends on what you mean by internal redirect, I presume you just want to execute another route's action. You have a few options of what you can do. This seems to be working for me:
val canonicalEndpoint = get("/first/route") {
//do things in here
}
Then you could subsequently do:
get("/second/route")( canonicalEndpoint.action )
And I think you would get your desired response.
I like saving the whole Route response of the get() as you may also want to use that with scalatra's url() function in routing.