I have a problem with Redirect in Scala play framework.
How can I redirect to view BooksController.index() ? In documentation they suggest to use Redirect but I don't know how.
def edit(Id: Int) = Action {
val book: Book = Book.findById(Id)
Ok(views.html.edit())
}
def update = Action {
implicit request =>
val (id, title, price, author) = bookForm.bindFromRequest.get
val book: Book = Book.findById(id)
book.id = id
book.title = title
book.price = price
book.author = author
Redirect(routes.BooksController.index())
}
Now can recognize --> import play.api.mvc.Results._
But i have an error --> "object java.lang.ProcessBuilder.Redirect is not a value"
If you would really like to continue using reverse routing in your code instead of having string uri values all over the place, see this:
Redirect with Bad Request Status.
The Redirect function accepts only a String or Call.
Try the following steps:
0) Add in the BookController
import play.api.mvc._
1) Add the following string in your route config file(hard disk location: controllers/BooksController)
GET /redirectedPage controllers.BooksController.index
2) Define a variable in the BookController
val Home = Redirect(routes.BookController.index())
3) Describe in the BookController
def update = Action {
implicit request => Home
}
Also do "sbt clean; sbt compile" to recompile auto-calls in ReverseRoutes.scala.
Well done.
The last line in the update action is the Redirect call which redirects to BooksController index route
import play.api.mvc.Results._
Redirect(routes.BooksController.index())
Related
I have an internationalized Scala Play 2.7.x WebApp and have the usual routes e.g.
GET / controllers.ApplicationController.index
GET /page/somePage/ controllers.SomeController.somePage
GET /contact controllers.ContactController.view
Now I'd like to add a new route that will basically change-language-redirect to any target route. I implement this use-case by adding an additional route on top of routes like this:
GET /$lang<(en|es)> controllers.ApplicationController.langRedirect(lang: String, target: String = "")
The idea is that every time you do e.g.
http://localhost:9000/en => will go to home page in english
http://localhost:9000/en/contact => will go to contact page in english
http://localhost:9000/es => will go to home page in spanish
http://localhost:9000/es/contact => will go to contact page in spanish
and so on. Unfortunately it doesn't always work e.g. the one included before /en/page/somePage/ it will not match it correctly to the first rule:
GET /$lang<(en|es)> controllers.ApplicationController.langRedirect(lang: String, target: String = "")
presumably because of the intermediate / ... how can I fix that?
For completeness here is my ApplicationController.langRedirect(...) implementation:
def langRedirect(lang: String, target: String = "") = silhouette.UserAwareAction.async { implicit request =>
Future.successful(Redirect("/" + target).withLang(Lang(lang)))
}
Using Router.withPrefix, you can add langage code prefix to all your routes.
Here is an example.
package handlers
import javax.inject.Inject
import play.api.http._
import play.api.i18n.{ Langs, Lang }
import play.api.mvc.{ Handler, RequestHeader }
class I18nRequestHandler #Inject()(
webCommands: play.core.WebCommands,
optDevContext: play.api.OptionalDevContext,
router: play.api.routing.Router,
errorHandler: HttpErrorHandler,
configuration: HttpConfiguration,
filters: HttpFilters,
langs: Langs)
extends DefaultHttpRequestHandler(
webCommands, optDevContext, router, errorHandler, configuration, filters) {
def getLang(request: RequestHeader): Lang = {
// Get the first path
request.path.tail.split('/').headOption
.flatMap(path => Lang.get(path))
// language from the fist path, if it is in "play.i18n.langs (application.conf)"
.filter(lang => langs.availables.exists(_ == lang))
// Or preferred language, refereeing "Accept-Languages"
.getOrElse(langs.preferred(request.acceptLanguages))
}
override def handlerForRequest(request: RequestHeader): (RequestHeader, Handler) = {
// To use the language code from the path with MessagesApi,
// Replace "Accept-Languages" to the language from the path.
val requestWithLang = request.withHeaders(
request.headers.replace(HeaderNames.ACCEPT_LANGUAGE -> getLang(request).code))
super.handlerForRequest(requestWithLang)
}
override def routeRequest(request: RequestHeader): Option[Handler] = {
val lang = getLang(request)
request.path.tail.split('/').headOption
// If the first path is right language code (if not, Not Found)
.filter(_ == lang.code)
// Route this request with language code prefix
.flatMap(_ => router.withPrefix("/" + lang.code).handlerFor(request))
}
}
To enable I18nRequestHandler, you have to add it to "application.conf".
play.http.requestHandler = "handlers.I18nRequestHandler"
Also add supported languages to "application.conf".
play.i18n.langs = [ "en", "es" ]
This code forces all routes to have the language code prefix. If you need a exceptional routes such as "/" to let users choose its language, create custom routes and add it on routeRequest method.
Hope this is what you want ;)
OK found a possible solution that's to add a second top route that will take any possible target including /, the top of my routes file now look like this:
GET /$lang<(en|es)> controllers.ApplicationController.langRedirect(lang: String, target: String = "")
GET /$lang<(en|es)>/*target controllers.ApplicationController.langRedirect(lang: String, target: String = "")
GET / controllers.ApplicationController.index
GET /page/somePage/ controllers.SomeController.somePage
GET /contact controllers.ContactController.view
Why I need two? because of the home page can only be http://localhost:9000/en and can't be http://localhost:9000/en/
However, I will be happy to learn (and accept) a better/simpler solution.
I am uploading a videos and images using web-service and save the images in our application. When i save the files, the files are save on root of application folder. I want to access those images and videos with localhost url, like: I upload the file and save under app-root/upload/image.jpg. In my route mapping file, i declare routing as below:
GET /uploads/ staticDir:/upload
As define in Play Documentation. But still getting an compile time error: Controller method call expected. I want to access image like this http://localhost:9999/uploads/image.jpg
Well... One way of doing this is by adding following routes,
GET /uploads/*file controllers.Assets.at(path="/uploads", file)
But, it will interfere with the reverse-routing of already existing route which is,
GET /assets/*file controllers.Assets.at(path="/public", file)
And then you will have to use your these two assets routes as - #route.Assets.at("public", filename) and #route.Assets.at("uploads", filename) which means all your templates which use you public assets route as - #route.Assets.at(filename) will have to be changed. Which can be a hassle in an existing big project.
You can avoid this by using following method,
Create another controller as,
package controllers
object FileServer extends Controller {
def serveUploadedFiles1 = controllers.Assets.at( dicrectoryPath, file, false )
// Or... following is same as above
def serveUploadedFiles2( file: String ) = Action.async {
implicit request => {
val dicrectoryPath = "/uploads"
controllers.Assets.at( dicrectoryPath, file, false ).apply( request )
}
}
}
The above should have worked... but seems like play does a lot of meta-data checking on the requested "Assets" which somehow results in empty results for all /uploads/filename requests. I tried to look into the play-source code to check, but it seems like it may take sometime to figure it out.
So I think we can make do with following simpler method ( It can be refined further in so many ways.).
object FileServer extends Controller {
import play.api.http.ContentTypes
import play.api.libs.MimeTypes
import play.api.libs.iteratee.Enumerator
import play.api.libs.concurrent.Execution.Implicits.defaultContext
def serveUploadedFiles(file: String) = Action { implicit request =>
val fileResUri = "uploads/"+file
val mimeType: String = MimeTypes.forFileName( fileResUri ).fold(ContentTypes.BINARY)(addCharsetIfNeeded)
val serveFile = new java.io.File(fileResUri)
if( serveFile.exists() ){
val fileContent: Enumerator[Array[Byte]] = Enumerator.fromFile( serveFile )
//Ok.sendFile(serveFile).as( mimeType )
val response = Result(
ResponseHeader(
OK,
Map(
CONTENT_LENGTH -> serveFile.length.toString,
CONTENT_TYPE -> mimeType
)
),
fileContent
)
response
}
else {
NotFound
}
}
def addCharsetIfNeeded(mimeType: String): String =
if (MimeTypes.isText(mimeType)) s"$mimeType; charset=$defaultCharSet" else mimeType
lazy val defaultCharSet = config(_.getString("default.charset")).getOrElse("utf-8")
def config[T](lookup: Configuration => Option[T]): Option[T] = for {
app <- Play.maybeApplication
value <- lookup(app.configuration)
} yield value
}
But this method will cause some troubles in case of packaged-build deployments.
Which means, using the Play's Asset thing would be wiser choice. So looking again, the controllers.Assets.at which is actually controllers.Assets.assetAt uses this method at one place,
def resource(name: String): Option[URL] = for {
app <- Play.maybeApplication
resource <- app.resource(name)
} yield resource
Which means, it tries to locate the resource in the directories which are part of application's classpath and our uploads folder sure is not one of them. So... we can make play's Assets.at thingy work by adding uploads to classpath.
But... thinking again... If I recall all folders in the classpath are supposed to be packaged in the package to be deployed in-case of packaged-build deployments. And uploaded things will be created by the users, which means they should not be a part of package. Which again means... we should not be trying to access our uploaded things using Play's Assets.at thingy.
So... I think we are better off using our own simpler rudimentary implementation of serveUploadedFiles.
Now add a route in route file as,
GET /uploads/*file controllers.FileServer.serveUploadedFiles( file:String )
Also... Keep in mind that you should not be thinking of using play to serve your uploaded assets. Please use nginx or something similar.
I am using playframework 2.6 and play-slick 0.8.0.
Action code:
def addCompany = Authenticated {
DBAction(parse.json) {
implicit rs => {
val newCompany = rs.request.body
val result = CompanyTable.insert(newCompany.as[Company])(rs.dbSession)
if(result > 0)
Ok("{\"id\":"+result+"}")
else
Ok("New company was not created.")
}
}
}
The Action is a composition of an Action that just checks for a valid session and the DBAction, which requires the request body to have a valid JSON object.
Test code:
"should create a Company from a Json request" in new InMemoryDB {
val newCompany = Company(name = "New Company1")
val fr = FakeRequest(POST, "/company")
.withSession(("email", "bob#villa.com"))
.withHeaders(CONTENT_TYPE -> "application/json")
.withJsonBody(Json.toJson(newCompany))
val action = controllers.CompanyController.addCompany
val result = action(fr).run
status(result) should be_==(OK)
(contentAsJson(result) \ "id").as[Long] should be_>(1L)
}
The InMemoryDB class is just a FakeApplication with a pre-populated in memory database.
The issue that I am having is that when the test runs the result is always a 400 with body content containing a message saying [Invalid Json]. When I call the service using curl with the same JSON body content, it works and the id is returned.
I decided to build a separate test project, and I used the activator to create a seed for the new project. I noticed that in the generated test that a different method of calling the action was used, so I switched my project to use this method. It worked, but I don't know why.
New code:
"should create a Company from a Json request" in new InMemoryDB {
val newCompany = Company(name = "New Company1")
val action = route(
FakeRequest(POST, "/company")
.withSession(("email", "bob#villa.com"))
.withHeaders(CONTENT_TYPE -> "application/json")
.withJsonBody(Json.toJson(newCompany))
).get
status(action) should be_==(OK)
(contentAsJson(action) \ "id").as[Long] should be_>(1L)
}
As you can see it uses a call to route instead of calling the controller.
I have the following use-case. I implemented a very simple authentication in my play app which adds a session cookie if a user logs in (See code below).
This code works fine so far. What I want to achieve now is to check in my main template if a user is logged in or not and display login/logout elements on the page according to the user status.
How can I achieve this in the most elegant way?
I have found sources where people access the session variables directly from the template with play <= 2.1. It seems like this method doesn't work for 2.2 anymore and is deprecated?
Do I have to pass a boolean value in every action to the template to define if a user is logged in??
Wrapper Action
case class Authenticated[A](action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Future[SimpleResult] = {
if (request.session.get("user").getOrElse("").equals("user")) {
action(request)
} else {
Future.successful(Redirect("/login").withSession(("returnUrl", request.path)))
}
}
lazy val parser = action.parser
}
Submit Part of Login Controller
def submit = Action { implicit request =>
loginForm.bindFromRequest.fold(
errors => Ok(html.login.form(errors)),
requestUser => {
val user: String = Play.current.configuration.getString("fillable.user").getOrElse("")
val password: String = Play.current.configuration.getString("fillable.password").getOrElse("")
if (requestUser.name.equals(user) && requestUser.pw.equals(password))
Redirect(request.session.get("returnUrl").getOrElse("/")).withSession(session + ("user" -> requestUser.name) - "returnUrl")
else
Ok(html.login.form(loginForm, "error", Messages("error.wrongCredentials")))
})
}
Example Controller Action where Authentication is needed
def submit = Authenticated {
Action.async { implicit request =>
...
}
}
So what I found out now is that if the Controller Action uses an implicit request(like the one in my question above) I can use that request and therefore the session in my template if I add this to the head of the template:
(implicit request: Request[Any])
I am not sure if this is a good approach so I am happy if someone can approve it.
I have a Play 2.0 framework that is working well and I want to be able to add a specific get parameter (known only by be) to all routes. That parameters should be ignore by routes.
I explain.
Suppose I have routes like :
GET /add/:id controllers.MyController.add(id : Int)
GET /remove/:id controllers.MyController.remove(id : Int)
What I want is, for example, that http://mydomain.com/add/77?mySecretParam=ok still goes to controllers.MyController.add(id : Int) and then I could get mySecretParam in request object. And the same for all my routes.
Do you have any idea how can I do ?
Thanks.
Greg
package controllers
import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
object Application extends Controller {
def mySecretParam(implicit request: Request[_]): Option[String] = {
val theForm = Form(of("mySecretParam" -> nonEmptyText))
val boundForm = theForm.bindFromRequest
if(!boundForm.hasErrors)
Option(boundForm.get)
else
None
}
def index = Action { implicit request=>
Ok(views.html.index(mySecretParam.getOrElse("the default")))
}
}
Here's Java:
Your route
GET /hello/:id controllers.Application.hello(id: Int)
in Application controller
public static Result hello(int id){
//Retrieves the current HTTP context, for the current thread.
Context ctx = Context.current();
//Returns the current request.
Request req = ctx.request();
//you can get this specific key or e.g. Collection<String[]>
String[] param = req.queryString().get("mySecretParam");
System.out.println("[mySecretParam] " + param[0]);
//[req uri] /hello/123?mySecretParam=ok
System.out.println("[Request URI] "+req.uri().toString());
System.out.println("[Hello-ID]: " + id); //the function parameter in controller
return ok("[Hello-ID]: " + id + "\n[mySecretParam] " + param[0]);
}
Your console output
[info] play - Application started (Dev)
[Request] GET /hello/123?mySecretParam=imhereyee
[mySecretParam] imhereyee
[Request URI] /hello/123?mySecretParam=imhereyee
[Hello-ID]: 123
The key to your question is Context object and Request object from that