A question about asset fingerprinting in Play.
How to ask Play to embed ETags in URLs without using a third-party plugin?
E.g., if /css/resource.css had the ETag of 1234, then it would become /css/responsive-1234.css.
Related questions: Custom ETag algorithm for asset fingerprinting & Automatically Insert ETag (asset fingerprinting) as comment at top of the resource
There'll be the "out" routing and the "in" reverse routing. Firstly the "out" routing:
package controllers
import play.api.mvc._
object Resources extends Controller {
def fingerprintedUrl(path: String): String = {
// calculates ETag and add to URL path
val pathWithEtag = addEtagToPath(path)
pathWithEtag
}
}
Then in templates use the following for example:
<script src="#Resources.fingerprintedUrl("js/toolkit.js")"></script>
Now the reverse routing, add the following to the same controller:
def at(path: String): Action = {
val pathWithoutEtag = removeEtagFromPath(path)
Assets.at("/public", pathWithoutEtag)
}
Then in routes:
GET /resources/*file controllers.Resources.at(file)
Related
I'm working with playframework for final project at university and I'm getting a problem when routing a delete or put method.
When I'm requesting a DELETE or PUT methods I'm getting:
[info] play.api.Play - Application started (Dev)
[debug] a.ErrorHandler - onClientError: statusCode = 404, uri = /Rest/deleteCity, message ="
My JQuery ajax call is:
$("#scalaDelete").click(function(){
$("#result").empty();
$.ajax({
url: "http://localhost:9000/Rest/deleteCity",
method: "DELETE",
data: {city: "Alvorada"},
dataType: "json",
success: function(result){
$("#result").append("Result: "+result.Result);
},
error: function (request, status, error) {
alert(status);
}
});
});
My Route Play Route:
DELETE /Rest/deleteCity controllers.RestController.deleteCity()
My Controller Method:
case class UserDelete(city:String)
class RestController #Inject()(db: Database, cc: ControllerComponents) extends AbstractController(cc) {
val userDeleteForm = Form(
mapping(
"city" -> text
)(UserDelete.apply)(UserDelete.unapply)
)
def deleteCity = Action{ implicit request=>
val userPar = userDeleteForm.bindFromRequest.get
//DatabaseDelete
Ok(jsonResult)
}
}
I've already activated cross domain in chrome, I've used a CORS extension for it.
Thanks for helping
This seems related to Restful http delete in play, i.e. DELETE with data can be sketchy.
Instead of passing data, I would just move this to the url:
DELETE /Rest/deleteCity/:city controllers.RestController.deleteCity(city: String)
# or with a query string
DELETE /Rest/deleteCity controllers.RestController.deleteCity(city: String)
and then do
http://localhost:9000/Rest/deleteCity/Alvorada
# or with a query string
http://localhost:9000/Rest/deleteCity?city=Alvorada
Personally I prefer the latter.
I agree with #AndyHayden.
Play ignores the body of the DELETE request, that is the correct behavior to my mind, but you can work around by explicitly passing a body parser:
def delete = Action(parse.json) { implicit request =>
val json = request.body
val someProp = (json \ "someprop").as[String]
Ok(s"Prop is: $someProp")
}
(this example was given by one of the developers of the Play itself:
https://github.com/playframework/playframework/issues/4606#issuecomment-109192802.)
About the doubts in comments:
I've seen another post here where a guy said some browsers just support get and post method.
POST and GET are only valid for the method attribute of the form tag.
You are using javascript request, so you can use any method that server supports. i.e. DELETE is completely fine there.
But something interesting for you to know is that playframework uses akka and this framework does not support DELETE request for security reasons, in fact it wasn't well explained on post. Then if you wanna make a DELETE method you gotta make a post method for complete your code.
Akka HTTP supports the DELETE request (as well as Play Framework): https://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/directives/method-directives/delete.html
I want to serve a static file from a Scala Play controller. I am looking for something that would allow me to do something like this example below.
NOTE: This obviously does not work.
It is very possible that I am look at the problem in the wrong way, I however do NOT want to redirect to the app.html
def loadApplication(): EssentialAction = Action.sync { request =>
val contents = Assets.contentsOf("/public/assets/app.html") //This doesnot return the contents, but that is what I want
Ok(contents)
}
You can just use the Assets and return contents via that. You might have to tweak the path though:
class MyController #Inject() (assets: Assets) extends Controller {
def loadApplication(): Action[AnyContent] = Action.async { request =>
assets.at("/public/assets/", "app.html").apply(request)
}
}
More information may be found in the documentation: https://www.playframework.com/documentation/2.5.x/AssetsOverview#The-Assets-controller
Also note that you can map a route to your assets instead of statically referencing the file from the controller, like so:
GET /assets/*file controllers.Assets.at(path="/public", file)
I have many controllers in my play 2.4.x application.
I want to get a list of all route URLs pointing their respective controllers. I know how to get the URL from the current request. But I need a list of all the URLs available inside a play application.I want to generate this list dynamically because URLs can be changed/added/deleted in future.
So,is there some way I can generate this URL list dynamically ? Or do I have the obligation to store all the URLs statically somewhere in cache or dictionary ?
I obtained the desired list by using the documentation method provided by Router trait. The documentation method returns Seq[(String, String, String)] .Here each tuple has the format:
( {http-method} , {url} , {controller method} )
The Router trait is extended by all the autogenerated Routes.scala classes. Scala-compiler generated a separate Routes.scala for each routes file in the application. These auto-generated Routes.scala files implement all the methods of Router trait including the documentation method that we discussed above.
So,to get list of all URLs, I simply had to inject the Router trait and then access the documentation method:
import play.api.routing.Router
class MyClass #Inject()(router: Router) {
def getAllURLs:Seq[String] = router.documentation.map(k => k._2)
}
Update for Play 2.7, Scala:
class MyController #Inject()(routesProvider: Provider[play.api.routing.Router]) {
lazy val routes: Seq[(String, String, String)] = routesProvider.get.documentation
}
from discussion for play 2.6
In response to the accepted answer: Note that if you're working in a Controller and you try to inject the Router, you'll get a Circular Dependency error (because the Router depends on your Controller). You can fix this by doing the following:
private final Provider<Router> routerProvider;
#Inject
public MainController(Provider<Router> routerProvider) {
this.routerProvider = routerProvider;
}
And then
// later in execution
Router router = routerProvider.get();
if (!router.documentation().isEmpty()) {
html.append("<ul>");
router.documentation().forEach(doc ->
html.append("<li>")
.append("<b>Method</b>: ")
.append(doc.getHttpMethod())
.append(" <b>Path</b>: ")
.append(doc.getPathPattern())
.append(" <b>Controller</b>: ")
.append(doc.getControllerMethodInvocation())
.append("</li>"));
html.append("</ul>");
}
Is there a way to reconfigure the Grails 3 Link Generator to create Restful links, i.e. localhost:8080/book/{id} rather than the old style that includes the action in the URL, localhost:8080/book/show/{id}?
I'd like to have restful URLs in the location headers of the responses to save actions.
I've been using this Grails Restful Link Generator as a workaround. I'm not perfectly happy with it, but it's the best I've been able to come up with thus far.
1. Create a trait in src/main/groovy that removes the superfluous action from the URL
import grails.web.mapping.LinkGenerator
trait RestfulLinkGeneratorTrait {
LinkGenerator grailsLinkGenerator
String generateLink(Map map) {
map.controller = map.controller ?: this.controllerName
map.absolute = map.absolute ?: true
map.action = map.action ?: "show"
grailsLinkGenerator.link(map).replace("/$map.action", "")
}
}
2. Implement the RestfulLinkGenerator on your controller(s) and call generateLink(id: obj.id) to generate links.
#Secured('ROLE_USER')
class BookController extends RestfulController implements RestfulLinkGeneratorTrait {
//... other methods ...//
#Transactional
def save() {
// ... save you resource ... //
response.addHeader(HttpHeaders.LOCATION, generateLink(id: book.id))
respond book, [status: CREATED, view: 'show']
}
//... other methods ...//
}
What’s the preferred way to handle 404 errors with Play 2.0 and show a nice templated view?
You can override the onHandlerNotFound method on your Global object, e.g.:
object Global extends GlobalSettings {
override def onHandlerNotFound(request: RequestHeader): Result = {
NotFound(views.html.notFound(request))
}
}
Please note that there are really two different problems to solve:
Showing a custom 404 page when there is "no handler found", e.g. when the user goes to an invalid URL, and
Showing a custom 404 (NotFound) page as a valid outcome of an existing handler.
I think the OP was referring to #2 but answers referred to #1.
"No Handler Found" Scenario
In the first scenario, for "no handler found" (i.e. invalid URL), the other answers have it right but to be more detailed, per the Play 2.1 documentation as:
Step 1: add a custom Global object:
import play.api._
import play.api.mvc._
import play.api.mvc.Results._
object Global extends GlobalSettings {
override def onHandlerNotFound(request: RequestHeader): Result = {
NotFound(
views.html.notFoundPage(request.path)
)
}
}
Step 2: add the template. Here's mine:
#(path: String)
<html>
<body>
<h1>Uh-oh. That wasn't found.</h1>
<p>#path</p>
</body>
</html>
Step 3: tweak your conf/application.conf to refer to your new "Global". I put it in the controllers package but it doesn't have to be:
...
application.global=controllers.Global
Step 4: restart and go to an invalid URL.
"Real Handler can't find object" Scenario
In the second scenario an existing handler wants to show a custom 404. For example, the user asked for object "1234" but no such object exists. The good news is that doing this is deceptively easy:
Instead of Ok(), surround your response with NotFound()
For example:
object FruitController extends Controller {
def showFruit(uuidString: String) = Action {
Fruits.find(uuidString) match {
case Some(fruit) => Ok(views.html.showFruit(fruit))
// NOTE THE USE OF "NotFound" BELOW!
case None => NotFound(views.html.noSuchFruit(s"No such fruit: $uuidString"))
}
}
}
What I like about this is the clean separation of the status code (200 vs 404) from the HTML returned (showFruit vs noSuchFruit).
HTH
Andrew
If you want to do the same using Java instead of Scala you can do it in this way (this works for play framework 2.0.3):
Global.java:
import play.GlobalSettings;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Http.RequestHeader;
public class Global extends GlobalSettings {
#Override
public Result onHandlerNotFound(RequestHeader request) {
return Results.notFound(views.html.error404.render());
}
}
Asumming that your 404 error template is views.html.error404 (i.e. views/error404.scala.html).
Please note that Play development team are making lots of efforts to move away from global state in Play, and hence GlobalSettings and the application Global object have been deprecated since version 2.4.
HttpErrorHandler.onClientError should be used instead of
GlobalSettings.onHandlerNotFound. Basically create a class that inherits from HttpErrorHandler, and provide an implementation for onClientError method.
In order to find out type of error (404 in your case) you need to read status code, which is passed as a one of the method arguments e.g.
if(statusCode == play.mvc.Http.Status.NOT_FOUND) {
// your code to handle 'page not found' situation
// e.g. return custom implementation of 404 page
}
In order to let Play know what handler to use, you can place your error handler in the root package or configure it in application.conf using play.http.errorHandler configuration key e.g.
play.http.errorHandler = "my.library.MyErrorHandler"
You can find more details on handling errors here: for Scala or Java.
This works in 2.2.1. In Global.java:
public Promise<SimpleResult> onHandlerNotFound(RequestHeader request) {
return Promise.<SimpleResult>pure(notFound(
views.html.throw404.render()
));
}
Ensure that you have a view called /views/throw404.scala.html
This works in 2.2.3 Play - Java
public Promise<SimpleResult> onHandlerNotFound(RequestHeader request) {
return Promise<SimpleResult>pure(Results.notFound(views.html.notFound404.render()));
}
html should be within /views/notFound404.scala.html
Dont forget to add Results.notFounf() and import play.mvc.Results;
For Java, if you want to just redirect to main page, I solved it by this.
#Override
public Promise<Result> onHandlerNotFound(RequestHeader request) {
return Promise.pure(redirect("/"));
}