Documentation TODO
smart_client format changed whitelisting errors make sure context function is correct local clients (include maxSockets info) require('http').globalAgent.maxSockets = 200; wire format changed
dry: core api
Installing
To install:
npm install dry-api
Introduction
This package provides the core api libraries for the dry framework. Dry api's are elegant to write, and feature role based, declaritive security and validations.
They are transport agnostic, and protocol agnostic. You can run them REST over HTTP, you can run JsonRPC over TCP or HTTP.
The look
// server side
var api = api_manager.api("example_api", true);
api.public("hello_messages", function(callback, name, age){
var name_message = "Hello " + name + ".";
var age_message = "You're " + age + " years old.";
callback(null, "You're " + age + " years old.");
});
// node or browser side
client.example_api().hello_messages("Kendrick", 30, function(err, name_message_response, age_message_response){
if(err){ throw(err); }
console.log(name_message_response); // "Hello Kendrick."
console.log(age_message_response); // "You're 30 years old."
});
// iOS side
client.example_api().hello_messages("Kendrick", 30, { (err, name_message_response, age_message_response) in
if(err){ throw(err); }
println(name_message_response); // "Hello Kendrick."
println(age_message_response); // "You're 30 years old."
});
Defining an API
Roles
An api is a set of functions. Each api function has a name, and a "role" that can access it. There are a set of default security roles, but you can define your own if you like.
The default roles are: "server", "admin", "user", and "public".
Functions marked with the "server" role are never served across the network. They can only be accessed through loopback.
Functions marked with the "public" role can be accessed by anyone. There is no access validation performed.
Functions marked with the "user" or "admin" role can only be accessed by users with those roles.
You can think of roles as a thing a user has, an array of strings var user = { ..., roles: ["user", "admin"], ... }
.
If a user is an admin, you should assign them the "admin" role, for example.
You can connect your existing security methodology to this api very easily, or you can use ours.
You can learn more about roles, and security below in the section "Bring Your Own Security".
Api's are defined as groups of functions in a namespace. Imagine an api that looks like:
api_functions : { post: { get(id) -> (err, post), // everyone can access this. save(post) -> (err, new_id) // only users, can access this }, example: { echo(message) -> (err, message) // anyone can access this hello(name, age) -> (err, messaeg, age_plus_one) // anyone can access this } }
// server side var dry_api = ; var access_manager = ;var api_manager = access_manager; var example_api = api_manager; // create an api named "example" // this function can be called by anyone because it has the role "public"example_api; // this function can be called by anyone because it has the role "public"example_api; var post_api = api_manager; // create an api named "post" // this function can be called by anyone because it has the role "public"post_api; // this function can only be called by users with the role "user"post_api; // client side <script> var client = "/api"; client; client; client; client
Validations
Each function can have type validations on the parameters:
// server side var example_api = api_manager; // create an api called "example" // if you don't call it with a string, and a number, you'll get an error, and the function will never even be run example_api; // client side apiexample; apiexample; apiexample; apiexample;
Documentation past this point it not up to date. Some of it is close.
Serving an API
These API's are transport agnostic, and protocol agnostic. You can run them rest over http, you can run jsonrpc over tcp or http.
var server = ; var dry_api = ; var access_manager = ;var api_manager = access_manager; var api = api_manager;// or, if you combined it with the example above, you could add example_api like so:// api_manager.api("example", example_api); api; api; api; { var expires = null; access_manager;} { var app = ; ;}; ;
Consuming an API
With all this meta information, we can produce smart clients for a variety of languages. The first one we tackled is js, we produce one for the server and one for the client.
This is an example of a basic call, without a smart client:
var api = "http://localhost:8000/api"; api; api;
If we output a hash from the api_manager class we can create a smart client, that knows the names of our api functions.
var api = "http://localhost:8000/api"; apiexample; apiexample;
We can also have dry_api.client
produce a file for consumption on the client side.
// server side var code = ;fs;
// client side <script src="dry.underscore.js"></script><script src="my_api_client.js"></script> <script> apiexample; apiexample; </script>
Securing an API
You can define "apis" as local only, that is they can only be accessed in memory. Clients won't even know they exist. They get "unknown_method" errors, instead of "permission_error" errors.
The more you method.expects, the better. The api will validate the input before it ever gets to you.
Access Table
The api knows about a request parameter called "access_token", it will interact with the access_manager class you see, above to fetch a record from the store that contains authentication information.
You can provide it to your client like so:
var api = "/api"; api; api; api; api;
Bring Your Own Security
This example works with the http_rpc provider, because the req, and res object are made available on the context object. The context object has a property called "roles". "roles" is what the api looks at to determine what access the current caller has, and as a result what function they should have access to, if any. Appending context is also how you would make the current "user" available to your api functions. I'm assuming you have session enabled, and you're storing the current users user_id in the session.
api_manager; var api = api_manager; // if context.roles contains "user" we get this functionapi; // if context.roles contains "admin" we get this functionapi;
What role gets priority if, say context.roles === ["user", "admin"] can be seen in access_manager.roles().hash(); You can create your own roles if you want, when you create your access_manager;
Parameters, Callbacks, Request Formats
Optionally a set of parameter validations. the simplest way to call the api is with a method name, and an array of parameters:
var example_api = ; example_api; // post: { method: "example.hello_named_parameters", params: ["0", "1"], "0": "kendrick", "1": 30 }// recv: { params: [null, "Hello kendrick, you're 30 years old.", 40] }
if the api supports named parameters, you can make prettier requests:
var example_api = ; example_api; // post { method: "example.hello_named_parameters", name: "kendrick", age: 30 }// error shown for clarity, but if there isn't one we don't send it// recv: { /* error: null, */ params_map: ['error', 'message', 'age_plus_ten'], message: "Hello kendrick, you're 30 years old.", age_plus_ten: 40 }
you can fake named parameters if the api you're consuming doesn't support them, by adding a map_params array in your request.
var example_api = ; example_api; // post { method: "example.hello_named_parameters", name: "kendrick", age: 30, params_map: ['name', 'age'] }// recv: { params_map: ['error', 'message', 'age_plus_ten'], message: "Hello kendrick, you're 30 years old.", age_plus_ten: 40 }
A request and reply should either have a params array, or a params_map, never both. A malformed reply may have both, but a params array will always take prescedence. A malformed request may have both, but a params array always will take prescedence.
License
See the LICENSE file in the root of the repo for license information.