Grails: How do I map a 404 with /** - grails

I've tried to create a custom 404 url mapping for URL's that are not found:
"/test" {
controller="test"
}
"404" {
controller="application"
action="send404"
}
"500" {
controller="application"
action="send500"
}
But for some reason, the controller and action are never called. I get the default container 404 page. So, instead I tried:
"/test" {
controller="test"
}
"/**" {
controller="application"
action="send404"
}
"500" {
controller="application"
action="send500"
}
Which seems to work fine, except that it also seems to call the send404 action on every request. For example, if I hit /test, I see the test page, but I also get the log statement I made in the send404() action.
Ideas appreciated...

Have you tried killing whitespace in your declaration, as outlined in this answer?
"404"(controller:'application', action:'send404')
There is also an open issue GRAILS-4232 about this topic.

In grails, there is an ErrorController, the one that render stacktrace on 500, etc.
class UrlMappings {
static mappings {
"403" (controller: "error", action: "forbidden")
"404" (controller: "error", action: "notFound")
"500" (controller: "error", action: "internalError")
}
}
And then, you can render(controller:"error", action"notFound") in another controller to stay RESTful. Or it will automagically render the notFound action of the error controller.
More details here : http://groovy.dzone.com/articles/grails-exception-handling-http

Perhaps it's favicon.ico that's being requested by the browser on each request that's causing this to happen.
// Route 404 to this action to see!
def send404() {
log.error("404 Page Not Found! " + request.forwardURI)
response.status = 404;
render(view:"/application/not-found.gsp")
}

Related

Render GSP in filter is not working

I'm trying to render a .GSP view inside the view folder from my filter. The following code show that:
def filters = {
all(controller:'*', action:'*') {
afterView = { Exception e ->
if (controllerName) {
//some code here
if (annotation!=null) {
switch(response.format){
case 'all':
if(!response.containsHeader("AC_MSG")|| !response.containsHeader("AC_STATUS")){
render(view: "/internalerror", model: [controller: controllerName,action:currentAction,
message:"Response doesn't contain required headers AC_MSG or AC_STATUS. Either add the required headers or use json format.",
example:"Add the following response headers: AC_MSG:response message , AC_STATUS: false or true"
])
return false
}
break
default:
render status: 406
break
}
}
}
}
}
}
The problem is that this page didn't get rendered even the code is executed. The page is on the view directory directly. What I did wrong?
Thanks,
I don't think a filter can render a gsp, but controllers can.
A perfect example of what you want to do is available in the docs: filters
Basically you create an action inside a controller that renders the page, and the filter just redirects to the action.
case 'all':
if(!response.containsHeader("AC_MSG")|| !response.containsHeader("AC_STATUS")) {
redirect(controller: "someController", action:"someAction")
return false
}
Make ErrorController.groovy and implement action with this render view and params.
In filter use only redirect. Remove 'return false' statement also.

Grails UrlMapping Redirect to keep DRY

I am working with Grails 2.1.1 and would like to add a handful of customized URLs that map to Controller Actions.
I can do that, but the original mapping still works.
For example, I created a mapping add-property-to-directory in my UrlMappings as follows:
class UrlMappings {
static mappings = {
"/add-property-to-directory"(controller: "property", action: "create")
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
}
}
Now, I can hit /mysite/add-property-to-directory and it will execute PropertyController.create, as I would expect.
However, I can still hit /mysite/property/create, and it will execute the same PropertyController.create method.
In the spirit of DRY, I would like to do a 301 Redirect from /mysite/property/create to /mysite/add-property-to-directory.
I could not find a way to do this in UrlMappings.groovy. Does anyone know of a way I can accomplish this in Grails?
Thank you very much!
UPDATE
Here is the solution that I implemented, based on Tom's answer:
UrlMappings.groovy
class UrlMappings {
static mappings = {
"/add-property-to-directory"(controller: "property", action: "create")
"/property/create" {
controller = "redirect"
destination = "/add-property-to-directory"
}
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
}
}
RedirectController.groovy
class RedirectController {
def index() {
redirect(url: params.destination, permanent: true)
}
}
It is possible to achieve this:
"/$controller/$action?/$id?" (
controller: 'myRedirectControlller', action: 'myRedirectAction', params:[ controller: $controller, action: $action, id: $id ]
)
"/user/list" ( controller:'user', action:'list' )
and in the action you get the values normallny in params:
log.trace 'myRedirectController.myRedirectAction: ' + params.controller + ', ' + params.action + ', ' + params.id
As of Grails 2.3, it is possible to do redirects directly in the UrlMappings, without the need for a redirect controller. So in case you ever upgrade, you can redirect in UrlMappings like so, as per the documentation:
"/property/create"(redirect: '/add-property-to-directory')
Request parameters that were part of the original request will be included in the redirect.
In recent Grails version (currently 5.x) you need to pass a Map into the redirect: property:
"/viewBooks"(redirect: [uri: '/add-property-to-directory'])
https://docs.grails.org/latest/guide/theWebLayer.html#redirectMappings

Grails UrlMapping 404

I have this:
static mappings = {
"/a/b/$id/stuff"(controller: "stuff", action "action1" )
"/a/b/$id/stuff/$stuffId"(controller: "stuff", action "action2" )
"/a/b/$id/stuff/$stuffId/c"(controller: "stuff", action "action3" )
}
I can hit action1 and action2, but I can't hit action3, as it returns a 404.
What's going on?
def method(){
}
will not work.
def method = {
}
WILL work.
have you checked the code of your view? I guess if it can't compile, you'll get a 404.
At least, that's what I experienced.

Grails: Return 404 & show my 'Not Found' page for invalid id

What's the best way to handle invalid ids in a Grails controller action?
When MyDomainClass.get(params['i']) returns null in my controller action I want the user to see my custom 'Not Found' page and for a 404 HTTP response code to be returned - I can't figure out the cleanest way to do this.
Thanks.
I've used the following in my controllers, where 'notFound' is a custom 404 page:
def show = {
def referenceData = ReferenceData.get( params.id )
if (referenceData)
{ return [ referenceData : referenceData ] }
else
{ redirect(uri:'/notFound') }
}
I also mapped the custom error pages in UrlMapping.groovy, something like
static mappings = {
"403"(controller: "errors", action: "forbidden")
"404"(controller: "errors", action: "notFound")
"500"(controller: "errors", action: "serverError")
}
or
static mappings = {
"403"(view: "/errors/forbidden")
"404"(view: "/errors/notFound")
"500"(view: "/errors/serverError")
}
Grails Docs - mapping to response codes
EDIT - I apologize for misreading your question. the render method takes a status code. So in the controller, if nothing is found, try
render status: 404
or
render view: you_not_found_view
or both (in one render call).
Here's my magic formula for doing this. Maybe there's a better way, but this one works and ensures the same 404 view renders whether you generate the 404 or grails does it internally (no controller found, for example).
First, create a View class that extends AbstractView:
class NotFoundView extends AbstractView {
#Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) {
response.sendError(HttpServletResponse.SC_NOT_FOUND)
}
}
Next, create an error controller:
class ErrorController {
def notFound = {
return render(view: '/error/notFound')
}
}
Now create your error view under views/error/notFound.gsp:
<g:applyLayout name="main">
<!doctype html>
<html>
<head>
<title>Oops! Not found!</title>
</head>
<body>
<h1>Not Found</h1>
<section id="page-body">
<p>Nothing was found at your URI!</p>
</section>
</body>
</html>
</g:applyLayout>
It's crucial that you use the <g:applyLayout> tag. If you use your layout will render twice and nest itself.
Now for the URL mapping:
"404"(controller: 'error', action: 'notFound')
You're all set now to send that 404 from your controller:
def myAction = {
Thing thing = Thing.get(params.id)
if (!thing) {
return new ModelAndView(new NotFoundView())
}
}
This approach also lets you easily log the 404, try to resolve it and send a 301, or whatever you want to do.

Disable grails Searchable plugin default search page?

I'm trying to disable the Searchable plugin default search page (http://localhost/searchable/), but haven't found a way to do it. Anyone know how this can be done, preferably in a legit way, but resorting to trickery if necessary?
I usually re-route error code handlers to a controller so I can do some logging or whatever before rendering the view. You can use that here also:
class UrlMappings {
static mappings = {
"/searchable/$action?"(controller: "errors", action: "urlMapping")
"/$controller/$action?/$id?" { }
"/"(view:"/index")
"403"(controller: "errors", action: "accessDenied")
"404"(controller: "errors", action: "notFound")
"405"(controller: "errors", action: "notAllowed")
"500"(view: '/error')
}
}
where ErrorsController looks something like this:
class ErrorsController {
def accessDenied = {}
def notFound = {
log.debug "could not find $request.forwardURI"
}
def notAllowed = {}
def urlMapping = {
log.warn "unexpected call to URL-Mapped $request.forwardURI"
render view: 'notFound'
}
}
and you'll need to create accessDenied.gsp, notFound.gsp, and notAllowed.gsp in grails-app/errors
By sending a 'hidden' controller to its custom mapping you can log unexpected access to it, but still render the 404 page to hide its existence.

Resources