Play 2.2.1 Scala - Redirect for 413 REQUEST_ENTITY_TOO_LARGE - scala

I have the following Controller action as form post resolver:
def importCompletionsSubmit(indexName: String) = AuthenticatedAction {
Action.async {
implicit request => {
completionsForm.bindFromRequest().fold(
errors => Future.successful(Ok(html.crudindex.importCompletionsForm(indexName, errors))),
completions => {
val autoCompletionService = new AutoCompletionService(new Elasticsearch)
autoCompletionService.importCompletions(indexName, completions.text) map {
result: BulkImportResult =>
if (result.error) Redirect(routes.ListIndices.index(Option.empty[String])).flashing("error" -> Messages("error.bulkItemsFailed", result.failures))
else Redirect(routes.ListIndices.index(Option.empty[String])).flashing("success" -> Messages("success.completionsAdded", result.requests))
}
}
)
}
}
}
I know that I can change the max length value for this action but what I would like to do is sending the user back to the form with a nice error message when he enters too much text.
If the request body is exceeding the default max length I get a completly blank page and only the browser console shows "413 (Request Entity Too Large)". I tried to catch this error in my global object but that did not change anything. It seems to me that the global onError trigger is not entered when a parser sends back an errorpage. Still a blank page. I also tried to catch that error inside the action but it seems to me that the action code is not entered because the body parser is already throwing this blank error page.
Is there a way to send the user back to the form action when the body exceeds the max length?

Something like this should work for you:
def test = Action.async(parse.maxLength(1024, parse.multipartFormData)) { implicit request =>
Future(request.body match {
case Left(MaxSizeExceeded(length)) => Ok(your_pretty_error_page.scala.html)
case Right(body) => {
...
}
})
}

Related

Getting the Play Framework to redirect to a Thank You page after file download

I have the following code for the download of a file and then redirecting to a thank you page in the DownloadController.scala under the Play Framework
def thankView = SecuredAction(WithProvider[AuthType](CredentialsProvider.ID)) {
implicit request: SecuredRequest[DefaultEnv, AnyContent] =>
Ok(downloadThankView(request.identity))
}
def download = SecuredAction(WithProvider[AuthType](CredentialsProvider.ID)) {
implicit request: SecuredRequest[DefaultEnv, AnyContent] =>
val futureMaybeFile = downloadService.generateDownload(request.identity.userID)
val maybeFile = Await.result(futureMaybeTempFile, 10 second)
maybeFile match {
case Some(file) =>
Ok.sendFile(
file,
fileName = _ => Some(file.getName),
onClose = () => {
file.delete()
// position (1) for placing the redirect, which doesn’t prevent the file download, but does not get executed
Redirect(routes.DownloadController.thankView())
}
).withHeaders(
"HttpResponse.entity.contentType" -> "text/txt",
"Content-Disposition" -> s"attachment; filename=${file.getName}"
)
// position (2) for placing the redirect which executes but prevents the file download
Redirect(routes.DownloadController.thankView())
case None =>
Redirect(routes.InfoController.view(Messages(“oops.there.was.error"), Messages("download.title")))
}
}
When I place the Redirect(routes.DownloadController.thankView()) line in the onClose section of sendFile, it does not get executed; and when it is placed after the sendFile it executes but prevents the file from getting downloaded.
What am I missing in here? How can I solve this issue?
From a HTTP point of view, I think this is not something doable: a single HTTP response cannot contain at the same time a file and be a redirect to somewhere else.
You should rather have some kind of html page that:
triggers the file download (via JS for instance)
triggers a redirect to the "thank you" page

Sequence of maps not working - scala play framework

I'm having some problems when trying to map some different objects so that I can extract some fields from it.
I've the function in my controller like this:
def index = SecuredAction.async { implicit request =>
transportService.allNonActive().map { transports =>
val sourceEmailsListBuffer = ListBuffer[String]()
val destinyEmailsListBuffer = ListBuffer[String]()
val sortingCenterStockListBuffer = ListBuffer[SortingCenterStock]()
val transportsListBuffer = ListBuffer[Transport]()
transports.map { transport =>
transportsListBuffer.append(transport)
// gets SC of this transport
sortingCenterStockService.retrieve(transport.idSCStock).map { sortingCenterStock =>
Logger.debug(s"Entry on SCS")
sortingCenterStockListBuffer.append(sortingCenterStock)
}
// gets email from source
userDAO.find(transport.idSourceUser).map { option =>
option.map { user =>
user.email.map { email =>
sourceEmailsListBuffer.append(email)
Logger.debug(s"Entry on Source Email")
}
}
}
// gets email from destiny
userDAO.find(transport.idDestinyUser).map { option =>
option.map { user =>
user.email.map { email =>
destinyEmailsListBuffer.append(email)
Logger.debug(s"Entry on Destiny Email")
}
}
}
}
Logger.debug(s"Size of source emails: ${sourceEmailsListBuffer.size}")
Logger.debug(s"Size of destiny emails: ${destinyEmailsListBuffer.size}")
Logger.debug(s"Size of scs: ${sortingCenterStockListBuffer.size}")
Logger.debug(s"Size of transp: ${transportsListBuffer.size}")
Ok(views.html.transports.index(request.identity, sourceEmailsListBuffer.toList, destinyEmailsListBuffer.toList, sortingCenterStockListBuffer.toList, transportsListBuffer.toList))
}
}
When I load the page for the first time (with any minor change, i.e. I change the string I use to indicate what I'm debugging), it gets the info from the last map userDAO.find(transport.idDestinyUser).map. When I refresh the page, the list's size destinyEmailsListBuffer is 0 and it is returned to the view before doing the map (at least I think so).
This is what I get after refreshing, after getting the correct output for the first time:
second load of the page
Thanks in advance, I hope you can help me!
I think your general structure is wrong. For instance:
userDAO.find(transport.idDestinyUser).map { option =>
option.map { user =>
user.email.map { email =>
destinyEmailsListBuffer.append(email)
Logger.debug(s"Entry on Destiny Email") // This returns Unit!
}
}
}
So you are using map operations and chaining those results to other functions, but instead if returning lists of items, you are incrementing an existing list that is never returned. Either return destinyEmailsListBuffer after logging or re-write to use forEach and to pick up the right values from somewhere.

akka-http: How to set response headers

I've a route as follows:
val route = {
logRequestResult("user-service") {
pathPrefix("user") {
get {
respondWithHeader(RawHeader("Content-Type", "application/json")) {
parameters("firstName".?, "lastName".?).as(Name) { name =>
findUserByName(name) match {
case Left(users) => complete(users)
case Right(error) => complete(error)
}
}
}
} ~
(put & entity(as[User])) { user =>
complete(Created -> s"Hello ${user.firstName} ${user.lastName}")
} ~
(post & entity(as[User])) { user =>
complete(s"Hello ${user.firstName} ${user.lastName}")
} ~
(delete & path(Segment)) { userId =>
complete(s"Hello $userId")
}
}
}
}
The content type of my response should always be application/json as I've it set for the get request. However, what I'm getting in my tests is text/plain. How do I set the content type correctly in the response?
On a side note, the akka-http documentation is one of the most worthless piece of garbage I've ever seen. Almost every link to example code is broken and their explanations merely state the obvious. Javadoc has no code example and I couldn't find their codebase on Github so learning from their unit tests is also out of the question.
I found this one post that says "In spray/akka-http some headers are treated specially". Apparently, content type is one of those and hence can't be set as in my code above. One has to instead create an HttpEntity with the desired content type and response body. With that knowledge, when I changed the get directive as follows, it worked.
import akka.http.scaladsl.model.HttpEntity
import akka.http.scaladsl.model.MediaTypes.`application/json`
get {
parameters("firstName".?, "lastName".?).as(Name) { name =>
findUserByName(name) match {
case Left(users) => complete(users)
case Right(error) => complete(error._1, HttpEntity(`application/json`, error._2))
}
}
}

Spray request - Complete request only after nested futures

New to spray and scala. Been struggling to get it right for couple of days now.
I am trying to merge facebook oauth2 login + user login details into the database in case the same user logs in by different ways(user/pass or fb login).
Pasting below spray routing snippet.
path("facebook") {
post{
entity(as[JObject]) { json =>
val fb: FacebookAuthModel = json.extract[FacebookAuthModel]
complete {
//Get user details from fb oauth2
val fbUser = fbAuth.getIdentity(fb) match {
case Right(user: User) => user
case Left(error: Failure) => throw new FailureException(error)
}
//Check if user is already present either by fb id or email
val userFuture = userRepo(FetchUserByFacebook(fbUser.facebook.get,fbUser.email))
userFuture.map {
case u: User => {
//user present but fb id not attached yet
if (u.facebook.isEmpty) {
//update fb id for the user - fire to actor and forget, i.e no callback to sender
userRepo(UpdateFacebookId(u.id.get, fbUser.facebook.get))
}
//complete request with a token - request(1)
AuthToken(token=jwt.createToken(u))
}
case None => {
//first time user using fb login
userRepo(CreateUser(fbUser)).map {
//complete request with the token - request(2)
case createdUser: User => AuthToken(token=jwt.createToken(createdUser))
case None => throw new FailureException(Failure("Not able to CreateUser", FailureType.Unauthorized))
}
}
}
}
}
}
}
Everything works fine except in case of first time user using fb login (refer request(2)).Request gets completed with empty response before the nest future could complete.
I tried flatMapping the result from userFuture and then using onComplete on it to give the appropriate response, but it din't work.
Any idea how I could successfully complete the request(request(2)) with the token?
If one of the two branches in your code execution path could result in a Future, then you have to code to this as the lowest common denominator when dealing with userFuture. That means flatMap on userFuture and using Future.successful in the case where you don't have an explicit second Future to deal with. Something along this line:
def handleUserResult(a:Any):Future[AuthToken] = a match{
case u:User =>
if (u.facebook.isEmpty) {
userRepo(UpdateFacebookId(u.id.get, fbUser.facebook.get))
}
Future.successful(AuthToken(token=jwt.createToken(u)))
case None =>
userRepo(CreateUser(fbUser)).map {
case createdUser: User =>
AuthToken(token=jwt.createToken(createdUser))
case None =>
throw new FailureException(Failure("Not able to CreateUser", FailureType.Unauthorized))
}
}
Once you define that method, you can use it on userResult as follows:
userResult.flatMap(handleUserResult)
I didn't check this code for compilation issues. I was more trying to show the general approach of flatMap used to handle two cases, one that produces another second Future and one that does not.

Play 2.2 for Scala: is it possible to modify the session of an incoming request?

I'm trying to use action composition to add a fake user to Session.
def GuestAction(f: Request[AnyContent] => Result): Action[AnyContent] = {
Action { request =>
var myUser = searchUser(request.session)
if ( myUser == null ) {
myUser = newUser()
}
f(request).withSession("user" -> myUser)
}
}
In my controller there is
def action1 = GuestAction { implicit request =>
// My code
Ok()
}
def action2 = GuestAction { implicit request =>
val user = request.session.get("user").get
// My code
Ok()
}
When I open Chrome and browse to the route pointing to "action1" and then to the route pointing to "action2", everything works fine: I got a new user and it is attached to the session.
On the contrary, when I open Chrome and I browse to the route pointing to "action2" first, I got an error because my "request.session" is empty, and that's obvious: using .withSession() the session is attached to the Result, not to the incoming request.
So, in order to make this work, I need to attach the session key/value pairs to the incoming request - like it is possible in FakeRequest.withSession(), but there's no such method in Request.
What would you suggest in order to fix this issue?
You're very nearly there - but to get the maximum value from your Action composition, you should be able to make your "client" Actions totally unaware of the session - they really only care about the User object after all. You want to be able to write:
def action2 = GuestAction { implicit user => implicit request =>
// Do something with 'user', whether it's a guest or real
println(s"My user is $user")
Ok()
}
action2 doesn't care how the User was obtained, but it can rely on one being available. To make this happen, GuestAction needs to be something like this:
def GuestAction(f: (User) => Request[AnyContent] => Result): Action[AnyContent] = {
Action { request =>
var myUser = searchUser(request.session)
if ( myUser == null ) {
myUser = newUser()
}
f(myUser)(request)
}
}
The only remaining piece of the puzzle is putting a user into the session - which you can still do with a conventional response.withSession() as part of a successful login process.