We should create the REST APIs which can do the following :
The APIs should implement the following:
1. APIs should be RESTful.
2. APIs should support both XML and JSON response.
3. The sensitive information like passwords etc. should be exchanged over SSL.
4. The error conditions should be differentiated from each other clearly so that specific handlers can be written by the clients.
5. We should try to use the existing controller end points to expose the APIs. But , if we have to support multiple versions of APIs an the same time, we need to introduce a new set of end points for each version. The version information can be passed as a parameter.
Ex - http://api.crossbow.com/api/v1/courses
This is a cleaner and efficient way to support multiple versions at the cost of code duplication to some extent.
We can follow one of the following two mechanisms to implement authentication and authorization for APIs.
We can implement Oauth 2.0 to support authentication and authorization of the third party apps that access our APIs.
The implementation details can be found here - https://wiki.exphosted.com/doku.php/oauth_2.0_for_apis
This is a simple process where each site will have an api key and also each user will have an api key.
The keys are generated as soon as the site or the user is created. They will be usable after activating the site/user.
With every request that is being sent to the API, we need to include the api key. Based on the key, the user/site is found and the privileges are handled for them. The code that handles all this can be pulled out to a rails plugin.
We will follow OAuth 2.0 for authentication as it is the industry standard.
1. The application which needs the access is registered at - http:<siteurl>/client_applications/new
A registration email is sent.
2. The site admin will approve the site. An email notification is sent to the application owner with the consumer key and secret.
3. The client aplication sends a request to the token url of the api (http://api.crossbow.com/api/authenticate/token) with consumer key, consumer scecret, email and password of the crossbow user. If both the client application and user are authenticated and active an access token is returned in the response. Otherwise an error code is returned.
We expire access tokens every 60 days.
4. The client application sends requests to the api using the access token for next 60 days. When an invalid access token is sent the apis are not accessible.
5. When the access token is expired after 60 days a special exception with error code is generated and client handles this by performing step 3 again.
Oauth Token request parameters and response - https://wiki.exphosted.com/doku.php/oauth_2.0_for_apis#oauth_20_spec_discussion
Sample API request and response - https://wiki.exphosted.com/doku.php/rest_apis#sample_api_request_and_response
We use rabl gem (0.3.0) to construct our api response - https://github.com/nesquena/rabl
The error response will be constructed using a customized serialize module.
Lets consider the products api sample which lists all the products of the site and also allows to search through them.
Request URL - https://api.crossbow.com/api/v1/products
Parameters - access_token, format, :page, :per_page, :price_min, :price_max, :tag_id, :category_id, :order_by, :order
Only access_token is mandatory and the remaining are optional and have default values.
The default out put format is xml.
XML Response -
<products>
<product>
<product-type>LiveEvent</product-type>
<price>0.0</price>
<description>test live event</description>
<product-id>62</product-id>
<thumbnail-src-url> http://api.crossbow.com/images/icons/live_event_thumb.png </thumbnail-src-url>
<id>38</id>
<categories/>
<free>true</free>
<title>Test Live Event</title>
</product>
..........
..........
<products>
JSON Response -
{"products":
[{"product_type":"LiveEvent",
"price":0.0,
"description":"test live event",
"product_id":62,
"thumbnail_src_url":"http://api.crossbow.com/images/icons/live_event_thumb.png",
"id":38,
"categories":[],
"free":true,
"title":"Test Live Event"},
{......},
{......}
]
}
Any unsuccessful operation while accessing an api throws an exception which is handled and displayed along with the error code. The response is in the following format.
1. XML -
<error>
<description>access_token_expired</description>
<error-code>ERR0007</error-code>
</error>
2. JSON -
{"error":
{ "description":"access_token_expired","error_code":"ERR0007"}
}
The following error codes are used as of now -
| Error_code | Description | Comment |
|---|---|---|
| ERR0000 | message description depends on the error occurred. | Any un handled/generic Server error |
| ERR0001 | Resource not found #{resource_name} | The accessed resource/record is not found. |
| ERR0002 | Resource not saved #{resource_name} | When any record updation fails. |
| ERR0003 | Resource not accessible #{resource_name} | When current user has no privileges to access a resource. |
| ERR0004 | message description depends on the error occurred. | Custom error thrown by us. |
| ERR0005 | message description depends on the error occured. | Any oauth exception which is not specific to user. (ex - invalid client) |
| ERR0006 | invalid_user | When user authentication fails or user is inactive during the oauth token obtaining process. |
| ERR0007 | access_token_expired | When the current access token is expired. |
We can display the following status codes in the response so that proper warnings can be shown on the client. This can be added some time in the future.
200 OK— Used for all the successful operations
400 Bad Request— Used as a general catchall for bad requests. For example, missing parameters like api key or a validation error on a model
401 Unauthorized— Used when user accesses a resource that is not allowed to be accessed by him.
404 Not Found— Used whenrequesting a resource that doesn’t exists. This includes the api end points that are not found.
405 Method Not Allowed—Used when a client attempts to perform an operation on a resource that it doesn’t support (for example, performing a DELETE on a resource that can't be deleted).
503 Service Unavailable— Used when client reaches the throttle limit.
Also, all the operations which fail due to an exception or error should include a valid error message in the response.
We can throttle the API using a gem based on rack middle ware - https://github.com/datagraph/rack-throttle
Some of the instructions to use this gem inside our rails app can be found here - http://martinciu.com/2011/08/how-to-add-api-throttle-to-your-rails-app.html
We can specify the maximum number of requests per day or per hour as an option. This limit should only apply to our API calls.
Rate Limit - At this moment we can go with 1200 requests per hour per IP which means one 20 requests per minute. This number can be discussed and tweaked.
With every request to the API, a couple of headers will be appended in the response.
1. X-RateLimit-Limit - Shows the number of requests allowed per hour
2. X-RateLimit-Remaining - Shows the number of requests remaining in the current hour.
When the number of requests exceed the limit we can throw a 503 status code (Service Unavailable).
Rails 2 configuration - For rails 2 applications we need to config this gem in config/environments.rb if we need for all the environments or configure it only in the config/<environment>.rb where environment is the specific env you need this to work (Ex- staging).
The number of requests remaining at any time can be stored on a redis server or memcached.
We can store this information on redis server as we already use it for Chat application.
Note -
Redis seems to be ignoring the expiration if we update the value of any key. So, at this point of time there is no straight forward way to compute the count starting from the first request of the user.
So, every one hour of server, the count gets refreshed. It is not after one hour of client's first request.
To be updated