Let's say we have a grails web application exposing several resources.
tags
urls
users
The application has a classical web-interface which the users interact with and some administration.
We want to expose the resources from the application to clients via a RESTful API and we don't want that part of the app to clutter up the controllers and code we already have.
So we came up with the following:
If the web interface offers host/app_path/url/[list|show|create] we want the REST API to be at /host/app_path/rest/url.
So we ended up with the following UrlMappings file:
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{
}
/* adding new urls and listing them */
"/rest/url"{
controller = "urlRest"
action = [POST: "save", PUT: "save", GET: "list", DELETE:"error"]
}
/* accessing a single url */
"/rest/url/$id"{
controller = "urlRest"
action = [POST: "update", PUT: "update", GET: "show", DELETE: "delete"]
}
/* non-crud stuff on urls */
"/rest/url/$action?/$id?"{
controller = "urlRest"
}
"/"(view:"/index")
"500"(view:'/error')
}
}
The problem is, that this isn't exactly the most DRY thing here. It gets worse as we add more resources such as tags. They would translate to yet another three blocks of very similar code...
The non-crud functions will be things like searching with specific criterions and such...
We tried generating the mapping closures with a loop, but without success. Are we completely on the wrong track here?
I would recommend the following mapping:
"/rest/url/$id?"(resource:"urlRest")
Below is the HTTP method to action mapping that this would create for the urlRestController:
GET show
PUT update
POST save
DELETE delete
I see why you might want to map /rest/url POST to save and /rest/url/id PUT to update, but that goes against the meaning of those verbs. A PUT should be the only way to add a new url and POST the only way to update a url. Doing it the way you have laid out would work and might be the best way if your constraint is to keep your current controller code untouched. However, my guess is that you controller might already be coded to handle the default mappings just fine (update/delete give error if no id, show redirects to list if no id, etc.).
Related
I have a Grails controller with multiple actions. For now all actions are available for user calls (I can access them from my browser), even the ones that should be called only from withing the g:include tag. I want to restrict access to such actions from the browser. I cannot mark action as protected because in this case I will not be able to include this action in a view for another controller.
Is there any practice how to encapsulate actions in such situations?
The way to “protect” actions from being accessible via a URL is to not provide a URL mapping to them. The default url mapping looks something like this…
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
// ...
}
}
That “/$controller/$action?/$id?(.$format)?” mapping is convenient for simple crud apps and demos well but for any substantial app you should almost always remove that. Without it, only the actions you explicitly expose are accessible.
Anybody who already have implemented something similar using Grails could tell me please which are the good pratices (if there are any) to create user profile URLs with the format "http://www.myservice.com/username", as in Facebook, Twitter, Linkedin?
I'm trying to implement it through the UrlMappings and appears to me I'll need to break with the code conventions, at least for the Controllers.
So, any suggestions are welcome, thanks.
UPDATE 1
When I mentioned my concern about breaking the code conventions, what I'm saying is that I want to show the user profile using this mapping, but I do have other objects in my application which I would like to access using the default mapping:
"/$controller/$action?/$id?"()
SOLUTION
Thanks to the great contributions I've received here, I've camed up with this solution, which solves my problem.
As was pointed out, to do this kind of mapping, I'll need to control more closely how my requests are handled. That means I'll need to tell to Grails which controllers I don't want to be mapped to the "username" rule.
Since that will be a very tedious task (because I have several controllers), I did this to automate it:
UrlMappings.groovy
static mappings = {
getGrailsApplication().controllerClasses.each{ controllerClass ->
"/${controllerClass.logicalPropertyName}/$action?/$id?"(controller: controllerClass.logicalPropertyName)
}
"/$username/$action?"(controller: "user", action: "profile")
}
...
}
And of course, I'll need to do something similar in my user registration process to avoid usernames to be equal to some controller name.
That's it, thank you all.
Assuming you have a UserController and you are going to map any domain.com/username to the show action of user controller, your url mapping could be something like this :
In my example, name will become a parameter in your params.
for further details refer to here
Hope this helps.
static mappings = {
"/$name"(controller: "user", action: "show")
...
}
Given your requirements, everything after http://yourdomain.com/ can be a username or one of your other controllers, which can have undesired effects depending on which url mapping is defined first (e.g. user controller vs nonuser controller). But most likely the nonuser controller list will be the smaller list, so you should place that first and filter against it, then treat all other url mappings as user mappings.
Here is an example:
static mapping = {
"/$controller/$action?/$id?" {
constraints {
controller inList: ['nonUserController1', 'nonUserController2',...]
}
}
//this should work for /username and /username/whateveraction
"/$username/$action?"(controller: 'user')
}
Some things to note here:
you need to place the non user controller url mapping first, since everything else after http://yourdomain.com/ may be a username - correct?
second you need to place a constraint to catch all the non user controllers, while ignore the user url mappings
also you need to prevent a user from signing up with a username that matches one of your non user controllers
I am trying to create a mock rest service. There are basically two components to this. I need a controller with actions and views that allows me to create ResourceMappings where I create a mapping between a uri and a mock response that I will pass back. The second component is a catch-all Grails url mapping for every other url so that when they hit the uri of this application they will be sent to my catch all controller that will return the mapped response that they created earlier.
For example... I go to the url http://someserver.com:1234/restMapping/list. This url is the exception to the catch all rule. It takes me to some views that allow me to create a rest uri mapping. Here I create the mapping /mockservice/test and give it the response "This is a test" with a content type of text/plain. Now, if I go to the url http://someserver.com:1234/mockservice/test I should hit the catch all that sends me to a controller that returns a page with the content type text/plain and the response "This is a test".
I have tried the following and it does not seem to work. Does anyone have any ideas?
static mappings = {
"/$control/**" {
controller = "catchAllHandler"
action = "index"
constraints {
control(validator: {!['restMapping','css','js','images'].contains(it)})
}
}
"/$controller/$action?/$id?"{
}
"/"(controller:"restMapping", action="index")
"500"(view:'/error')
}
An interesting thing to note is that when I get rid of ** and add in tons of extra variables like $s1?/$s2?/$s3? etc then it does seem to work. The problem is that I don't know how long the uri is that I am trying to map so I would rather use the ** to catch everything exception the few exceptions that I have.
I finally figured it out. I needed to include WEB-INF in my list to exclude. I now use the static excludes field as well as the validator to exclude specific controller urls.
class UrlMappings {
static excludes = ["/images/*","/css/*","/js/*","/WEB-INF/*"]
static mappings = {
"/restResourceMapping/$action?/$id?"{
controller = "restMapping"
}
"/$control/?**" {
controller = "catchAllHandler"
action = "index"
constraints {
control(validator: {!['restMapping'].contains(it)})
}
}
"/"(controller:"restMapping", action="index")
"500"(view:'/error')
}
}
I decided to just exclude all url mappings that should never be any of my rest urls. These included /images, /css, /js, and /WEB-INF. I can now create urls of any length and have them go to my catch all controller. If the person goes to the base url or the restMapping url set then they will be taken to the crud pages where they can create new rest resource mappings. If I want to create any other controllers and views that I want to go around the catch all controller I can simply add it to my validator and make them process normally.
Also you might notice that I am using a ? in the catch all right after the /. This seems to make it so my catch all works with urls that only have one word after the server name as in http://server.com:1234/something.
I am not sure about this, but I think the order in which the URL mappings are defined is important. So try listing the URL mapping for your special cases at the beginning of the mappings closure and then list the general ones (the one that uses **). Do let me know if this works :)
In grails, I have a link like /myapp/questions/all
The all is a parameter (all, replied, ...) passed to my controller.
I have a form to search question depending of type : in all, in replied, ...
In the search form, I have an hidden field to pass parameter.
But the url displayed is /myapp/questions/ ans not /myapp/questions/all
So I tried with url : url="[action:'question', controller:'mycontroller', params:['monparam':'${mavariable}']]"
but it's not working.
Any idea ?
Thanks
You can do it like this:
class UrlMappings {
static mappings = {
name nameOfTheMapping: "/question/$para/" {
controller = "mycontroller"
action = "question"
}
...
Then you can access the mapping by:
<a href='${createLink(mapping: 'nameOfTheMapping', params: [para: para.encodeAsUrl()])}' title='test'>Test</a>
The above code is created in my taglib, so it maybe a little different if you want to use it in a view.
I don't completely understand your question, but it seems you are not following the grails convention. the url is of the form
/app/controller/action
so grails is interpreting the 'all' part of your url as the action to invoke on the questions controller (what I got from your 'link like /myapp/questions/all').
Where I got confused was with your url specification.
url="[action:'question', controller:'mycontroller', params:['monparam':'${mavariable}']]"
Based on that, you should have a controller called 'mycontroller', with an action called 'question' on it. The url you will see in the browser would be
/app/mycontroller/question?monparam:whatever
See here for details on controllers in general.
You need to edit grails-app/conf/UrlMappings.groovy and create a mapping to the controller that omits the action. (since you're handling this all within one action)
something like
"/questions/$question_type" (controller: 'questions', action: 'your_action')
where "your_action" is the name of the action that is processing these requests.
Then in QuestionsController.groovy:
def your_action = {
// use question_type as needed
def questions = Questions.findByQuestionType(params.question_type)
// etc.
}
You can do a variety of things to affect the mapping of urls to requests, check out the UrlMapping section in the Grails User Guide.
I want to build a ASP.NET MVC site so that the controller for a specific url is stored in the database instead of the URL.
The reason for that is that i'm building a CMS system and the users should be able to change the template (controller) without changing the URL. I also think that the name of the controller is not relevant for the end users and i want clean URL:s.
I realise that i could just add all routes at application start, but for a system with like 100 000 pages it feels like a bad idea.
Is it possible to store the url:s in the database and make a lookup for each request and then map that request to a specific controller?
Basically you'll have to implement your own IRouteHandler.
Part of the answer and some example code is in Option 3 of this question's answer:
ASP.NET MVC custom routing for search
More information:
http://weblogs.asp.net/fredriknormen/archive/2007/11/18/asp-net-mvc-framework-create-your-own-iroutehandler.aspx
Why couldn't you just do something like this:
-- Global.asax.cs --
routes.MapRoute(null, // Route name
"content/{id}", // URL with parameters
new { Controller = "Content", Action = "Show", Id = (string) null }); // Parameter defaults
-- /Controllers/ContentController.cs --
public class ContentController : Controller
{
public ActionResult Show(string id)
{
// Lookup the 'content' (article, page, blog post, etc) in the repository (database, xml file, etc)
ContentRepository repository = new ContentRepository();
Content content = repository.FindContent(id);
return View(content);
}
}
Such that a request to your site www.yoursite.com/content/welcome-to-my-first-blog-post would call ContentController.Show("welcome-to-my-first-blog-post").
I suppose ASP.NET can do many of the same things as PHP. If so there is a simple approach.
With rewrite rules you can easily send any traffic to any URL of the 100K to the same place. On that destination you could simply use the server variables containing the URL requested by the client and extract the location. Look it up in the DB and send the corresponding data for that URL back to the client on-the-fly.
"for a system with like 100,000 pages it feels like a bad idea."
It is a bad idea if you are creating a routing system that cannot be reused. The basic {controller}/{action}/{id} schema points you in the direction of reuse. This schema can be extended/revamped/recreated according to your needs.
Instead of thinking about how many pages you have think about how your resources can be grouped.
Instead of creating a heavy routing system why not create an anchor link control (ascx) which allows user to only add valid internal links. Keep a table in the db of your templates and their controllers to populate the control with it.