Restrpc Plugin

  • Tags: rpc, rest, api, annotation, apidoc
  • Latest: 0.9.7
  • Last Updated: 10 October 2013
  • Grails version: 2.1 > *
1 vote
Dependency:
compile ":restrpc:0.9.7"

 Documentation  Source  Issues

Summary

RESTRPC is an interceptor api that allows the user to associate more than one method/function per request method while still being compliant with the REST standard thus creating a ONE-TO-MANY pairing.

Installation

grails install-plugin restrpc

Description

This plugin is no longer being maintained. For newer functionality, please see the Grails Api Toolkit

Grails RestRPC Plugin

RestRPC is an api implementation combining the methodologies of REST / RPC / HATEOAS. It is an interceptor allowing you to set annotations onto your controllers methods instantly enabling them to be RESTFULLY called. This differs from existing Grails functionality in that:

  • you can have as many rest calls as you want of ANY method you want
  • return different data to view vs api
  • can return complex objects, map, multiple domains, list or whatever
  • autogenerate apidocs
  • requesting client determines returned media type (JSON/XML)

Implementation

On first adding the plugin, RestRPC will create several variables in your config. The first of which you are initially concerned with are the 'apiName' and 'apiVersion'. These have default values which can be changed per environment:

restrpc.apiName = 'api'
restrpc.apiVersion = '1.0'

To implement in your controllers, make sure to import the restrpc classes:

import net.nosegrind.restrpc.*

Then remove static allowedMethods in your controller and add the service:

def restRPCService

Then start adding annotations to the methods you wish to be called via an api with the request method you are going to call them through:

@Api(method=RestMethod.GET,description='show something upon sending corresponding id')
def showSomething(Long id) { … }

NOTE: Do not add annotations to methods that REDIRECT as this will throw an error; Obviosly this is bad form but to avoid alot of questions in the forums, this would be why you got that error.

With RestRPC, you can add as many GET, POST, PUT and DELETE methods as you want in your controller. As with REST, it is good form to make sure that you are matching the request method with a 'proper' function (ie DELETE request method with a 'delete' function). Naturally you can deviate from this (just as with REST) but I'm sure you have good reasons, right? :)

Documentation Annotations

The above is just the basic functionality just to get the api to work. But if you want to coument the api, you will need a bit more. Below is an example of a a method with alot more documentation which we will walk through.

@Api(method=RestMethod.GET,description='get user base upon id',
	values=[
		@Params(paramType='Long',name='id',description='id of user',required=true)
	],
	returns=[
		@Params(paramType='String',name='fname',description='User first name',required=true),
		@Params(paramType='String',name='lname',description='User last name',required=true),
		@Params(paramType='Email',name='email',description='User email address',required=true)
	],
	errors=[
		@ErrorCode(code=404,description='page not found')
	]
)
def getUser(Long id) {...}

values are the values sent to the api. returns are the values returns from the api. errors are the error messages sent for each code from the api.

For paramType, it merely expects a string. There are a variety of different systems and datatypes/constraints so we don't try to put a constraint on the datatype so string is all that is required for showing it in the docs since we are not uasing this for processing.

Helper Methods

The RestRPCService has some usefule helper methods built into it that you can call in your functions to help you as well. For example, since the methods in your controller are shared with the view, you may not want them to return the exact same data to the api as they are returning to the view. Thus, you may want to do a 'api call detect' in the controller method like so:
@Api(method=RestMethod.GET,description='get user base upon id')
def getUser() {
  Long id = params.id.toLong()
  if(restRPCService.isAPICall()){
    // return data for api call
  }else{
    // return data for view
  }
  return
}

Helper Error Message Methods

The RestRPD service also makes it easy to handle error messages with a list of error message methods:
  • restRPCService._200_SUCCESS()
  • restRPCService._304_NOTMODIFIED()
  • restRPCService._404_NOTFOUND()
  • restRPCService._404_BADREQUEST()
  • restRPCService._403_FORBIDDEN()
All of these can be appended with additional error messages like so:
restRPCService._404_NOTFOUND("The User ${user} could not be found.")

You do not need to send success messages on successful api return as that will automatically be done for you. This message is mostly for succesful 'deletes' or updates wherein no data is returned.

Api Doc Generation

Once you have started annotating your controllers, you will be able to start viewing your apidocs. Just go to your apiname/apiversion/apidoc/show. By default the url will be as follows:
http://localhost/yourapp/api/1.0/apidoc/show

or if you have no context

http://localhost/api/1.0/apidoc/show

Authenticate

If you need to authenticate your api from a shell for testing, use the following with your credentials:
curl --data "j_username=admin&j_password=admin" http://localhost:8080/<yourapp>/j_spring_security_check --cookie-jar cookies.txt

Api Doc JSON Generation

The JSON in your APIDOCS is autogenerated based on the paramtype in your annotations and uses a 'restrpc.defaultData' value from your config that matches the paramtype. The default values generated in your config are as follows:
restrpc.defaultData.ID = '26'
restrpc.defaultData.String = 'Hello World'
restrpc.defaultData.Boolean = 'true'
restrpc.defaultData.Float = '1.00'
restrpc.defaultData.BigDecimal = '123567828794.87'
restrpc.defaultData.Integer = '18'
restrpc.defaultData.Long = '18926'
restrpc.defaultData.Email = 'example@yoursite.com'
restrpc.defaultData.Url = 'http://www.yoursite.com'

You can change the 'restrpc.defaultData' values in your config at any time and always add new ones for new paramtypes you wish to support. But if you are looking to override the default, you should set 'exampleData' in your Params/Param.

@Params(paramType='Url',name='website',description='a users website',required=true,exampleData='http://www.grails.org')

API

GET

curl --verbose --request GET http://localhost:8080/<yourapp>/restrpc/<controller>/<action>/JSON/1 --cookie-jar cookies.txt
curl --verbose --request GET http://localhost:8080/<yourapp>/restrpc/<controller>/<action>/XML/1 --cookie-jar cookies.txt
or in your code using HTTPBuilders RESTClient (as an example)…
try{
  def restrpc = new RESTClient('http://localhost:8080/<yourapp>')
  def path = '/restrpc/<controller>/<action>/JSON/1'
  def resp = restrpc.get(path:path)
  def data = restrpc.data
}catch(HttpResponseException ex){
  hre.printStackTrace()
}

POST (accepts formats of 'XML' or 'JSON')

curl --verbose --request POST --header "Content-Type: application/json" -d '{fname: "Richard",lname:"Mozzarella"}' http://localhost:8080/<yourapp>/restrpc/<controller>/<action>/JSON/1 --cookies-jar cookies.txt
curl --verbose --request POST --header "Content-Type: application/xml" -d '{fname:"Richard",lname:"Mozzarella"}' http://localhost:8080/<yourapp>/restrpc/<controller>/<action>/XML/1 --cookies-jar cookies.txt
or in your code using HTTPBuilders RESTClient (as an example)…
try{
  def restrpc = new RESTClient('http://localhost:8080/<yourapp>')
  def path = '/restrpc/<controller>/<action>/JSON
  def resp = restrpc.post(path:path,body:[fname:'Richard',lname:'Mozzarella'],requestContentType : URLENC )
  def data = restrpc.data
}catch(HttpResponseException ex){
  hre.printStackTrace()
}

PUT (accepts formats of 'XML' or 'JSON')

curl --verbose --request PUT --header "Content-Type: application/json" -d '{fname: "Richard",lname:"Mozzarella"}' http://localhost:8080/<yourapp>/restrpc/<controller>/<action>/JSON/1 --cookies-jar cookies.txt
curl --verbose --request PUT --header "Content-Type: application/xml" -d '{fname:"Richard",lname:"Mozzarella"}' http://localhost:8080/<yourapp>/restrpc/<controller>/<action>/XML/1 --cookies-jar cookies.txt
or in your code using HTTPBuilders RESTClient (as an example)…
try{
  def restrpc = new RESTClient('http://localhost:8080/<yourapp>')
  def path = '/restrpc/<controller>/<action>/JSON
  def resp = restrpc.put(path:path,body:[fname:'Richard',lname:'Mozzarella'],requestContentType : URLENC )
  def data = restrpc.data
}catch(HttpResponseException ex){
  hre.printStackTrace()
}

DELETE

curl --verbose --request DELETE http://localhost:8080/<yourapp>/restrpc/<controller>/<action>/JSON/1 --cookies-jar cookies.txt
or in your code using HTTPBuilders RESTClient (as an example)…
try{
  def restrpc = new RESTClient('http://localhost:8080/<yourapp>')
  def path = '/restrpc/<controller>/<action>/JSON/1'
  def resp = restrpc.delete(path:path)
  def data = restrpc.data
}catch(HttpResponseException ex){
  hre.printStackTrace()
}

Troubleshooting

The most common problem is forgetting to remove 'static allowedMethods' from your Controller. If you are having problems accessing your API, make sure you have removed this from your controller.

Also if you are unable to view the data and keep getting a 'view', making sure the URL does NOT have a trailing slash.

If you are retrieving the data via Javascript on the frontend, keep in mind not to return domain objects in your return response. Javascript cannot handle domain objects and you will need to return all data as a map if that is you intended target. RestRPC has the ability to return Objects in maps but always keep in mind that Javascript has no idea how to handle them so make sure to convert them or send an ID so they can be used in your GSP.