Grails Dynamic Rest Endpoint Mapping - grails

In my UrlMappings I have this mapping defined :
"/$controller/$action?/$id?(.$format)?"{}
and now I want to add a set of version 2 services.
for example :
A new service at URI : /api/myaction
and I want to be able define a new endpoint /api/v2/myaction , where myaction will map to a new action called myactionV2

There are a number of ways to do this and the best solution depends on some factors that you haven't included in your question. Here is a solution that most closely maps to the question and a comment that the OP added above.
See the project at https://github.com/jeffbrown/javaheadendpoints.
https://github.com/jeffbrown/javaheadendpoints/blob/47f41b3943422c3c9e44a08ac646ecb2046972d1/grails-app/controllers/demo/v1/ApiController.groovy
package demo.v1
class ApiController {
static namespace = 'v1'
def myaction() {
render 'This request was handled by version 1 of the api'
}
}
https://github.com/jeffbrown/javaheadendpoints/blob/47f41b3943422c3c9e44a08ac646ecb2046972d1/grails-app/controllers/demo/v2/ApiController.groovy
package demo.v2
class ApiController {
static namespace = 'v2'
def myaction() {
render 'This request was handled by version 2 of the api'
}
}
https://github.com/jeffbrown/javaheadendpoints/blob/47f41b3943422c3c9e44a08ac646ecb2046972d1/grails-app/controllers/demo/v3/ApiController.groovy
package demo.v3
class ApiController {
static namespace = 'v3'
def myaction() {
render 'This request was handled by version 3 of the api'
}
}
https://github.com/jeffbrown/javaheadendpoints/blob/47f41b3943422c3c9e44a08ac646ecb2046972d1/grails-app/controllers/javaheadendpoints/UrlMappings.groovy
package javaheadendpoints
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/$controller/$namespace/$action/$id?(.$format)?" {
// ...
}
"/"(view:"/index")
"500"(view:'/error')
"404"(view:'/notFound')
}
}
Sending requests yields what I think is the requested behavior:
$ curl http://localhost:8080/api/v1/myaction
This request was handled by version 1 of the api
$ curl http://localhost:8080/api/v2/myaction
This request was handled by version 2 of the api
$ curl http://localhost:8080/api/v3/myaction
This request was handled by version 3 of the api
Other options include using a Version http header but because of some of the wording above, I think that is not going to be exactly what you want.
I hope that helps.

not suppose to do that,The way i suggest is to split in two controller
/api1/myaction
/api2/myaction
or in action
/api/myaction1
/api/myaction2

Related

NestJS Swagger: Edit route name

I have an endpoint like this:
#Controller(['route1', 'route2'])
export class test {
#Post('endpoint')
public test() { return; }
}
The swagger than generates two routes:
'/route1/endpoint' and '/route2/endpoint'
I would like this to only generate one endpoint in the swagger documentation of '/endpoint', but then say in the documentation above the different routes the user can use to call the endpoint. Is there any way I can edit these and only show one?

Grails 2.5.5: Unable to access the request body of a PUT request?

I'm attempting to implement a RESTful API using Grails 2.5.5, and I'm running into a few issues.
It appears that Grails does not automatically map any methods for the corresponding HTTP methods, so I'm editing UrlMappings.groovy.
For example, take the following URLs:
GET /v1/1/persons/ <--- List of persons
POST /v1/1/persons/ <--- Create a new person
PUT /v1/1/persons/1234 <--- Edit person with ID of 1234
These are my url mappings:
"/v1/$appId/$controller/$action?/$id?(.$format)?" {
namespace = "v1"
}
"/v1/$appId/$controller"(action: "save", method: "POST") {
namespace = "v1"
}
"/v1/$appId/$controller/$id"(action: "update", method: "PUT") {
namespace = "v1"
}
So now, the first mapping will handle the GET request in my example urls as well as other generic urls.
The second mapping will handle the second url from my example urls.
And lastly, the third mapping handles the third url from my example urls.
The issue I'm facing now is that my command object isn't getting bound properly for my PUT request. The POST request works fine however.
These are my methods:
def save(MyCommand cmd) {
// works great
}
def update(MyCommand cmd) {
// cmd properties are null
// params.id is bound though. So I'm getting the path variable.
}
As you can see, the logic is very simple.
But I'm completely stumped as to why I can't get the request body in the PUT method.
Additional question: How can I get the above urls to work in addition to this url?:
/v1/1/persons/1234/status
I tried the following mapping, but it does not seem to work:
"/v1/$appId/$controller/$id/$action" {
namespace = "v1"
}
It feels like I'm stuck in this URLMappings hell!

Custom ApiExplorer with Namespace based ApiControllers

I'm trying to add API documentation at my backend system.
Default ApiExplorer and Help page worked absolutely great until the moment I introduced versions to my Api Controllers.
In order to add versions I created sub folders under the Controllers folder:
v1
v2
v3
and have version based Api Controllers there. In order to have my Api discoverable I have to rewrite DefaultHttpControllerSelector to take into account namespaces provided by any client and map them to right controllers:
http://backend.com/api/v1/controller/action
http://backend.com/api/v2/controller/action
This have broken my default ApiExplorer and the following property returns ZERO api descriptions
Configuration.Services.GetApiExplorer().ApiDescriptions
How can I customize existent ApiExplorer and help him to find my Api Controllers and not to rewrite whole ApiExplorer implementation. I really need just to show where to find my Api Controllers.
Please advise.
I will show you a way to do that. This code is just for learning. Here I not talking about design and best practices, so feel free to change anything you want.
Well, You must follow the next steps:
1) Create a custom ApiExplorer:
public class MyApiExplorer: ApiExplorer
{
private readonly string _version;
public MyApiExplorer(string version) : base(GlobalConfiguration.Configuration)
{
_version = version != null ? version.ToUpperInvariant() : "V1";
foreach(var apiDescription in ApiDescriptions)
{
apiDescription.RelativePath = apiDescription.RelativePath.Replace("{version}", _version);
}
}
public override bool ShouldExploreController(string controllerVariableValue, HttpControllerDescriptor controllerDescriptor,
IHttpRoute route)
{
return controllerDescriptor.ControllerType.FullName.Contains(_version);
}
}
a) In the constructor _version will be converted to upperCase (just in
case it will be passed as lowerCase) but if it is null then it will
take V1 as default. Then change relative path to show specific version
instead of {version}.
b) ShouldExploreController (in short words)
decide if specific controller is taken to show in documentation. In
this case we will only show controllers that its type full name contains
choosed version.
2) Go to HelpController class and change Index method like this:
public ActionResult Index(string version)
{
//...
Configuration.Services.Replace(typeof(IApiExplorer), new MyApiExplorer(version));
return View(Configuration.Services.GetApiExplorer().ApiDescriptions);
}
We are replacing current ApiExplorer by our own in order to be
returned when call to Configuration.Services.GetApiExplorer()
Now you can use this .../help?version=v1 or .../help?version=v2 or .../help?version=v3 and you will get specific api controller documentation.
Turned out that there is nothing to do with ApiExplorer. As instead you should modify your namespace based controller selector:
NamespaceHttpControllerSelector : DefaultHttpControllerSelector
{
//...
public override IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
{
var mapping = base.GetControllerMapping();
mapping["User"] = new HttpControllerDescriptor
{
Configuration = _httpConfig,
ControllerName = "User",
ControllerType = typeof(UserController)
};
//...
return mapping;
}
//... }
That is. After that default ApiExplorer will find you controllers and fetch all the actions.
I faced a similar problem recently, and solved mine with this:
2 LOC:
public class VersionControllerSelector : IHttpControllerSelector
to
public class VersionControllerSelector : DefaultHttpControllerSelector
...and...
public VersionControllerSelector(HttpConfiguration config)
to
public VersionControllerSelector(HttpConfiguration config) : base(config)

Groovy script to Grails app

Well I am new to Groovy/Grails. I have written a Groovy script that uses RESTClient to make HTTP POST request to JIRA server. The POST request sends a JQL query and receives the result in JSON format. Here's the full code:
import groovyx.net.http.RESTClient;
import groovyx.net.http.HttpResponseDecorator;
import org.apache.http.HttpRequest;
import org.apache.http.protocol.HttpContext;
import org.apache.http.HttpRequestInterceptor;
import groovy.json.JsonSlurper;
import static groovyx.net.http.Method.*
import static groovyx.net.http.ContentType.*
#Grab(value = 'org.codehaus.groovy:groovy-all:2.1.6',
initClass = false)
#Grapes([
#Grab(group = 'org.codehaus.groovy.modules.http-builder',
module = 'http-builder', version = '0.5.2'),
#GrabExclude('org.codehaus.groovy:groovy')
])
// connect to JIRA
def jiraApiUrl = 'http://my-jira.com/rest/api/2/'
def jiraClient = new RESTClient(jiraApiUrl);
// authentication
def basic = 'Basic ' + 'username:password'.bytes.encodeBase64().toString()
jiraClient.client.addRequestInterceptor (
new HttpRequestInterceptor() {
void process(HttpRequest httpRequest,
HttpContext httpContext) {
httpRequest.addHeader('Authorization', basic)
}
})
// http post method
def uriPath = 'search'
def param = [maxResults : 1, jql : '<jql-query>']
def Issues = jiraClient.post(requestContentType : JSON, path : uriPath, body : param)
def slurpedIssues = new JsonSlurper().parseText(Issues.data.toString())
println Issues.data.total
I need to migrate this script to a Grails app. Any suggestions as to how to do the same?
Define dependencies in BuildConfig (except the groovy dependency)
copy script contents to a Service
Possible extension:
use the grails rest plugin or grails rest-client-builder plugin instead of http-builder
Putting the logic into Service object will give you the ability to do dependency injection, which is native to grails services.
Also, you should consider using AsyncHTTPBuilder if your app has many users trying to make requests.
I strongly believe that the service response will be directly rendered to JSON
//your controller
class AbcController{
//your action
def save() {
render(abcService.save(params) as JSON)//your service response now been rendered to JSON
}
}
//your service class class AbcService {
def save(params){
....
return something
}
}

Grails webflow url rewriting

Want to make the URLs SEO friendly when using grails webflow. It is quite limiting with the convention grails uses and hard to go around the way it's built.
For example, i have a flow called fooProcess in the controller called FooController, when i trigger the flow i would like the display: /foo/bar/test, instead of /foo/fooProcess?excecution=e1s2
class FooController {
def fooProcessFlow {
showFoo {
}
}
}
I tried using redirect and specify the uri but that's not supported, grails complains that the page isn't found
fooProcessFlow {
showFoo {
redirect(uri:"/foo/bar/test")
}
}
grails/foo/fooProcess.dispatch/externalRedirect:/foo/bar/test
Also, a redirect is an end state in a flow, if I only want to render the page, i have to use the render method and specify the view name or structure my views according to webflow convention.
fooProcessFlow {
showFoo {
render(view:"/foo/bar/test")
on "add".to "add"
}
}
The url will be in this case
/foo/fooProcessProcess?execution=e6s1
Anyone dealt with this case before ?
Did anyone use UrlRweriteFilter with webflows in grails
http://code.google.com/p/urlrewritefilter/
ken
You can use URLMappings Grails Plugin
See: http://grails.org/doc/latest/ref/Plug-ins/URL%20mappings.html
Edit this file: grails-app/conf/UrlMappings.groovy
Putting something like this inside:
class UrlMappings {
static mappings = {
"/foo/bar/test" (controller: "foo", action: "fooProcessFlow")
"/$controller/$action?/$ids?"{
constraints {
}
}
}
}

Resources