How do I return assets with a 404 status code in Play? - scala

Currently I have a play application with Scala, in where I have the frontend in the public folder as a SPA.
I have a /404 route in where I want to return the frontEnd with the status code of 404.
However if I write:
def returnNotFound(): Action[AnyContent] = Action {
NotFound(assets.at("index.html"))
}
Then I get a compilation error because assets returns an Action, not a Writeable.
So how can I return the index.html in the public folder with the status code of 404?

The issue here is that assets.at("index.html") returns Action[AnyContent] which suppose to be return method of Controller, hence in order to change status you need to change result from assets.at like:
def returnNotFound(): Action[AnyContent] = Action.async { implicit request =>
assets.at("index.html")(request).map { result =>
new Result(result.header.copy(status = 404), result.body)
}
}

Related

Custom spray.io directive to validate request header value

I am new to spray and I am trying to write a custom directive. I would like the directive to reject the request if the header value is not valid otherwise leave the request alone.
I've tried to absorb this page:
http://spray.io/documentation/1.1.2/spray-routing/key-concepts/directives/
Specifically, the part about the responder chain. I'm trying to create something at the level of the bar Directive in the illustration. I'm just not getting how to pass the context unchanged to the inner route.
My else block below is not correct but expresses what I am trying to do. I just can't figure out how to implement it.
Any help would be greatly appreciated.
trait ApiKeyDirective {
import spray.routing.directives.HeaderDirectives._
import spray.routing.directives.BasicDirectives._
def validateApiKey(): Directive1 = {
headerValueByName("api-key") {key =>
val valid = key == "123"
if (!valid) reject() else pass
}
}
}
object ApiKeyDirective extends ApiKeyDirective
You can combine
headerValueByName:
def headerValueByName(headerName: String): Directive1[String]
with validate:
def validate(check: ⇒ Boolean, errorMsg: String): Directive0
For example:
def validateApiKey(route: Route) =
headerValueByName("api-key") { key =>
validate(key == "123", "Invalid API key") {
route
}
}
or without validate:
def validateApiKey(route: Route) =
headerValueByName("api-key") { key =>
if (key == "123")
route
else
reject(ValidationRejection("Invalid API key"))
}
Usage:
lazy val route = ...
... ~
pathPrefix("test_directive") {
get {
validateApiKey {
complete("ok")
}
}
} ~
...
Test from cmd/shell:
# curl http://localhost:8080/test_directive
Request is missing required HTTP header 'api-key'
# curl http://localhost:8080/test_directive -H 'api-key: bad'
Invalid API key
# curl http://localhost:8080/test_directive -H 'api-key: 123'
"ok"
I'm just not getting how to pass the context unchanged to the inner
route.
Spray does that for you!
Your code is mostly correct, there are just 2 simple problems to fix!
Firstly, you need to flatMap headerValueByName("api-key") directive.
Secondly, the return type will be Directive0 because the directive won't provide any value.
So final code would look like this:
object ApiKeyDirective {
import spray.routing.Directives._
val validateApiKey: Directive0 =
headerValueByName("api-key").flatMap { key =>
val valid = key == "123"
if (!valid) reject() else pass
}
}
Also, I recommend you to add a custom rejection to reject() block so that API users will be informed when their api key is invalid.

Is there a quick built in way to forward a request in the scala Play framework

I'm looking for something like
def proxy = Action.async { implicit req =>
//do something with req
val newRequest = req.map( r = r.path = "http://newurl");
forward(newRequest)
}
I saw that there is a redirect method but that only allows me to pass the request parameters and not everything else, headers, etc.
I am hoping there is something built in so I don't have to build it myself.
I'm not sure if this meets your requirements, but have you had a look into Play's WS.
The action forwardTo gets an url, fetches the according page and returns it as this request's response. It's not exactly like an forward in the Spring framework but it does the job for me.
/**
* Like an internal redirect or an proxy. The URL in the browser doesn't
* change.
*/
public Promise<Result> forwardTo(String url) {
Promise<WS.Response> response = WS.url(url).get();
return response.map(new Function<WS.Response, Result>() {
public Result apply(WS.Response response) {
// Prevent browser from caching pages - this would be an
// security issue
response().setHeader("Cache-control", "no-cache, no-store");
return ok(response.getBody()).as("text/html");
}
});
}
(I'm using Play 2.2.3)

Play 2.2.1 Scala - Redirect for 413 REQUEST_ENTITY_TOO_LARGE

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) => {
...
}
})
}

Scala Play framework 2.2 - How to get information about a users loginstatus in template

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.

Basic Play framework routing and web sockets example

I'm trying to learn how to use web sockets in Play 2.1, and I'm having trouble getting the web socket URL to work with my app's routing configuration. I started with a new Play application and the Play framework documentation on websockets.
Here is my conf/routes:
# Home page
GET / controllers.Application.index
# Websocket test site
GET /wstest controllers.Application.wstest
Then I added the wstest function to my controller class:
object Application extends Controller {
def index = Action {
Ok(views.html.index("Websocket Test"))
}
def wstest = WebSocket.using[String] { request =>
// Log events to the console
val in = Iteratee.foreach[String](println).mapDone { _ =>
Logger.info("Disconnected")
}
// Send a single 'Hello!' message
val out = Enumerator("Hello!")
(in, out)
}
}
However, so far, I can only access the websocket with the URL ws://localhost:9000/wstest (using the sample code at websocket.org/echo.html). I was looking at the sample/scala/websocket-chat app that comes with the Play framework, and it uses the routing configuration file to reference the websocket, like this:
var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
var chatSocket = new WS("#routes.Application.chat(username).webSocketURL()")
I tried replacing my websocket URL with #routes.Application.wstest.webSocketURL() and #routes.Application.wstest. The first one doesn't compile. The second one compiles, but the client and server don't exchange any messages.
How can I use my Play routing configuration to access this websocket? What am I doing wrong here?
Edit
Here is a screenshot of my compilation error, "Cannot find any HTTP Request Header here":
Without the compiler error it's hard to guess what might be the problem.
Either you have to use parens because of the implicit request, i.e. #routes.Application.wstest().webSocketURL(), or you have no implicit request in scope which is needed for the webSocketURL call.
Marius is right that there was no implicit request in scope. Here's how to get it in scope:
Update the index function in the controller:
def index = Action { implicit request =>
Ok(views.html.index("Websocket Test"))
}
Add the request as a curried parameter to index.scala.html:
#(message: String)(implicit request: RequestHeader)
#main(message) {
<script>
var output;
function init() {
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket() {
websocket = new WebSocket("#routes.Application.wstest.webSocketURL()");
.
.
.
And now the RequestHeader is in scope.