Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote-tracking branch 'upstream/master'

  • Loading branch information...
commit c13acd9b9c365ab35de4fff7db3bd238cf24b3ef 2 parents 19814e8 + d8f5732
Adam authored
1  Procfile
View
@@ -0,0 +1 @@
+web: node app.js
325 README.md
View
@@ -12,7 +12,6 @@ I/O Docs is a live interactive documentation system for RESTful web APIs. By def
levels in a JSON schema, I/O Docs will generate a JavaScript client interface. API calls can be executed from this interface, which are then proxied through the I/O Docs server with payload data cleanly formatted (pretty-printed if JSON or XML).
You can find the latest version here: [https://github.com/mashery/iodocs](https://github.com/mashery/iodocs)
-You can find a demo of it running here: [http://iodocs.demo.mashery.com](http://iodocs.demo.mashery.com)
However, we recommend that you install I/O Docs with *npm*, the Node package manager. See instructions below.
@@ -43,12 +42,13 @@ From the command line type in:
These will be automatically installed when you use any of the above *npm* installation methods above.
1. [express](http://expressjs.com/) - framework
-2. [oauth](https://github.com/unscene/node-oauth) - oauth library
+2. [oauth](https://github.com/ciaranj/node-oauth) - oauth library
3. [redis](https://github.com/mranney/node_redis) - connector to Redis
3. [connect-redis](https://github.com/visionmedia/connect-redis) - Redis session store
-4. [hashlib](https://github.com/brainfucker/hashlib) - used for signatures
-5. [querystring](https://github.com/visionmedia/node-querystring) - used to parse query string
-6. [jade](http://jade-lang.com/) - the view engine
+4. [querystring](https://github.com/visionmedia/node-querystring) - used to parse query string
+5. [jade](http://jade-lang.com/) - the view engine
+
+Note: hashlib is no longer a required module -- we're using the internal crypto module for signatures and digests.
RUNNING I/O DOCS
----------------
@@ -60,52 +60,52 @@ QUICK API CONFIGURATION EXAMPLE
-------------------------------
Adding an API to the I/O Docs configuration is relatively simple.
-1. Append the new top-level service information to the
- "./public/data/apiconfig.json" file.
-
- Example:
- <pre>
- "lowercaseapi": {
- "name": "Lower Case API",
- "protocol": "http",
- "baseURL": "api.lowercase.sample.com",
- "publicPath": "/v1",
- "auth": "key",
- "keyParam": "api_key_var_name"
- }
- </pre>
-
-2. Add the file "./public/data/lowercaseapi.json" to define the
- API.
-
- Example:
- <pre>
- {
- "endpoints": [
+First, append the new top-level service information to the `./public/data/apiconfig.json` file.
+
+Example:
+
+```js
+"lowercaseapi": {
+ "name": "Lower Case API",
+ "protocol": "http",
+ "baseURL": "api.lowercase.sample.com",
+ "publicPath": "/v1",
+ "auth": "key",
+ "keyParam": "api_key_var_name"
+}
+```
+
+Add the file `./public/data/lowercaseapi.json` to define the API.
+
+Example:
+
+```js
+{
+ "endpoints": [
{
- "name": "Resource Group A",
- "methods": [
- {
- "MethodName": "Method A1",
- "Synopsis": "Grabs information from the A1 data set",
- "HTTPMethod": "GET",
- "URI": "/a1/grab",
- "RequiresOAuth": "N",
- "parameters": [
- {
- "Name": "param_1_name",
- "Required": "Y",
- "Default": "",
- "Type": "string",
- "Description": "Description of the first parameter."
- }
- ]
- }
- ]
+ "name": "Resource Group A",
+ "methods": [
+ {
+ "MethodName": "Method A1",
+ "Synopsis": "Grabs information from the A1 data set",
+ "HTTPMethod": "GET",
+ "URI": "/a1/grab",
+ "RequiresOAuth": "N",
+ "parameters": [
+ {
+ "Name": "param_1_name",
+ "Required": "Y",
+ "Default": "",
+ "Type": "string",
+ "Description": "Description of the first parameter."
+ }
+ ]
+ }
+ ]
}
- ]
- }
- </pre>
+ ]
+}
+```
TOP-LEVEL SERVICE CONFIG DETAILS - apiconfig.json
-------------------------------------------------
@@ -113,16 +113,16 @@ The *apiconfig.json* file contains high-level information about an API.
### Example #1 - Explanation of each field in an example API config that uses basic key authentication:
-<pre>
-1. "lower": {
-2. "name": "My API",
-3 "protocol": "http",
-4. "baseURL": "api.lowercase.sample.com",
-5. "publicPath": "/v1",
-6. "auth": "key",
-7. "keyParam": "api_key_var_name"
-8. }
-</pre>
+```js
+"lower": {
+ "name": "My API",
+ "protocol": "http",
+ "baseURL": "api.lowercase.sample.com",
+ "publicPath": "/v1",
+ "auth": "key",
+ "keyParam": "api_key_var_name"
+}
+```
Line:
@@ -145,7 +145,7 @@ Line:
Ex: "/v1"
- In the Example #2 below, there is also "privatePath"
+ In the Example #3 below, there is also "privatePath"
which is used for endpoints behind protected resources.
6. "auth" key value is the auth method. Valid values can be:
@@ -163,29 +163,104 @@ Line:
---
-### Example #2 - Twitter API config that uses 3-legged OAuth
-
-<pre>
-1. "twitter": {
-2. "name": "Twitter API",
-3. "protocol": "http",
-4. "baseURL": "api.twitter.com",
-5. "publicPath": "/1",
-6. "privatePath": "/1",
-7. "booleanTrueVal": "true",
-8. "booleanFalseVal": "false",
-9. "auth": "oauth",
-10. "oauth" : {
-11. "type": "three-legged",
-12. "requestURL": "https://api.twitter.com/oauth/request_token",
-13. "signinURL": "https://api.twitter.com/oauth/authorize?oauth_token="
-14. "accessURL": "https://api.twitter.com/oauth/access_token",
-15. "version": "1.0",
-16. "crypt": "HMAC-SHA1",
-17. }
-18. "keyParam": "",
-19. }
-</pre>
+### Example #2 - Explanation of each field in an example API config that uses basic key authentication with signatures (signed call).
+
+```js
+"upper": {
+ "name": "Upper API",
+ "protocol": "http",
+ "baseURL": "api.upper.sample.com",
+ "publicPath": "/v3",
+ "auth": "key",
+ "keyParam": "api_key_var_name",
+ "signature": {
+ "type": "signed_md5",
+ "sigParam": "sig",
+ "digest": "hex"
+ }
+}
+```
+
+Line:
+
+1. Handle of the API. It is used to pull up the client
+ interface in the URL:
+
+ Ex: http://127.0.0.1:3000/upper
+
+2. "name" key value is a string that holds the name
+ of the API that is used in the Jade template output.
+
+3. "protocol" key value is either *http* or *https*
+
+4. "baseURL" key value is the host name of
+ the API calls (should not include protocol)
+
+5. "publicPath" key value is the full path prefix prepended
+ to all method URIs. This value often includes the version
+ in RESTful APIs.
+
+ Ex: "/v3"
+
+ In the Example #3 below, there is also "privatePath"
+ which is used for endpoints behind protected resources.
+
+6. "auth" key value is the auth method. Valid values can be:
+
+ "key" - simple API key in the URI
+ "oauth1" - OAuth 1.0/1.0a
+ "" - no authentication
+
+7. "keyParam" key value is the name of the query parameter that
+ is added to an API request when the "auth" key value from
+ (5) is set to "key"
+
+8. "signature" is a JSON object that contains the details about
+ the API call signing requirements. The signature routine coded
+ in app.js is a hash of the string concatenation of API key,
+ API key secret and timestamp (epoch).
+
+9. "type" key value is either *signed_md5* or *signed_sha256*.
+ More signature methods are available with crypto.js, but have
+ not been included in the code as options.
+
+10. "sigParam" key value is the name of the query parameter that
+ is added to an API request that holds the digital signature.
+
+11. "digest" key value is the digest algorithm that is used.
+ Values can be *hex*, *base64* or *binary*.
+
+12. Closing curly-bracket for the "signature" object
+
+13. Closing curly bracket for main object.
+
+
+---
+
+
+### Example #3 - Twitter API config that uses 3-legged OAuth
+
+```js
+"twitter": {
+ "name": "Twitter API",
+ "protocol": "http",
+ "baseURL": "api.twitter.com",
+ "publicPath": "/1",
+ "privatePath": "/1",
+ "booleanTrueVal": "true",
+ "booleanFalseVal": "false",
+ "auth": "oauth",
+ "oauth" : {
+ "type": "three-legged",
+ "requestURL": "https://api.twitter.com/oauth/request_token",
+ "signinURL": "https://api.twitter.com/oauth/authorize?oauth_token="
+ "accessURL": "https://api.twitter.com/oauth/access_token",
+ "version": "1.0",
+ "crypt": "HMAC-SHA1",
+ }
+ "keyParam": "",
+}
+```
Line:
@@ -276,49 +351,47 @@ You should look at the *./public/data/* directory for examples.
### Example #1 - Explanation of each field in an example API-level configuration
-<pre>
-1. {
-2. {
-3. "name":"User Resources",
-4. "methods":[
-5. {
-6. "MethodName":"users/show",
-7. "Synopsis":"Returns extended user information",
-8. "HTTPMethod":"GET",
-9. "URI":"/users/show.json",
-10. "RequiresOAuth":"N",
-11. "parameters":[
-12. {
-13. "Name":"user_id",
-14. "Required":"Y",
-15. "Default":"",
-16. "Type":"string",
-17. "Description":"The ID of the user",
-18. },
-19. {
-20. "Name":"cereal",
-21. "Required":"Y",
-22. "Default":"fruitscoops",
-23. "Type":"enumerated",
-24. "EnumeratedList": [
-25. "fruitscoops",
-26. "sugarbombs",
-27. "frostedteeth"
-28. ],
-29. "Description":"The type of cereal desired"
-30. },
-31. {
-32. "Name":"skip_status",
-33. "Required":"N",
-34. "Default":"",
-35. "Type":"boolean",
-36. "Description":"If true, status not included"
-37. }
-38. ]
-39. ]
-40. }
-41. }
-</pre>
+```js
+{
+ "name":"User Resources",
+ "methods":[
+ {
+ "MethodName":"users/show",
+ "Synopsis":"Returns extended user information",
+ "HTTPMethod":"GET",
+ "URI":"/users/show.json",
+ "RequiresOAuth":"N",
+ "parameters":[
+ {
+ "Name":"user_id",
+ "Required":"Y",
+ "Default":"",
+ "Type":"string",
+ "Description":"The ID of the user",
+ },
+ {
+ "Name":"cereal",
+ "Required":"Y",
+ "Default":"fruitscoops",
+ "Type":"enumerated",
+ "EnumeratedList": [
+ "fruitscoops",
+ "sugarbombs",
+ "frostedteeth"
+ ],
+ "Description":"The type of cereal desired"
+ },
+ {
+ "Name":"skip_status",
+ "Required":"N",
+ "Default":"",
+ "Type":"boolean",
+ "Description":"If true, status not included"
+ }
+ ]
+ }]
+}
+```
Line:
78 app.js
View
@@ -27,21 +27,20 @@
var express = require('express'),
util = require('util'),
fs = require('fs'),
- sys = require('sys'),
OAuth = require('oauth').OAuth,
query = require('querystring'),
url = require('url'),
http = require('http'),
+ crypto = require('crypto'),
redis = require('redis'),
- RedisStore = require('connect-redis')(express),
- hashlib = require('hashlib');
+ RedisStore = require('connect-redis')(express);
// Configuration
try {
var configJSON = fs.readFileSync(__dirname + "/config.json");
var config = JSON.parse(configJSON.toString());
} catch(e) {
- sys.puts("File config.json not found or is invalid. Try: `cp config.json.sample config.json`");
+ console.error("File config.json not found or is invalid. Try: `cp config.json.sample config.json`");
process.exit(1);
}
@@ -49,18 +48,16 @@ try {
// Redis connection
//
var defaultDB = '0';
-var db = redis.createClient(config.redis.port, config.redis.host);
-db.auth(config.redis.password);
-
-// Select our DB
-db.on("connect", function() {
- db.select(defaultDB);
- db.get("livedocs", function(err, reply) {
- if (config.debug) {
- console.log('Selected db \''+ defaultDB + '\' named \'' + reply + '\'');
- }
- });
-});
+var db;
+
+if (process.env.REDISTOGO_URL) {
+ var rtg = require("url").parse(process.env.REDISTOGO_URL);
+ db = require("redis").createClient(rtg.port, rtg.hostname);
+ db.auth(rtg.auth.split(":")[1]);
+} else {
+ db = redis.createClient(config.redis.port, config.redis.host);
+ db.auth(config.redis.password);
+}
db.on("error", function(err) {
if (config.debug) {
@@ -82,6 +79,13 @@ fs.readFile('public/data/apiconfig.json', 'utf-8', function(err, data) {
var app = module.exports = express.createServer();
+if (process.env.REDISTOGO_URL) {
+ var rtg = require("url").parse(process.env.REDISTOGO_URL);
+ config.redis.host = rtg.hostname;
+ config.redis.port = rtg.port;
+ config.redis.password = rtg.auth.split(":")[1];
+}
+
app.configure(function() {
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
@@ -317,7 +321,7 @@ function processRequest(req, res, next) {
privateReqURL = apiConfig.protocol + '://' + apiConfig.baseURL + apiConfig.privatePath + methodURL + ((paramString.length > 0) ? '?' + paramString : ""),
options = {
headers: {},
- protocol: apiConfig.protocol,
+ protocol: apiConfig.protocol + ':',
host: baseHostUrl,
port: baseHostPort,
method: httpMethod,
@@ -345,11 +349,15 @@ function processRequest(req, res, next) {
],
function(err, results) {
- var apiKey = reqQuery.apiKey || results[0],
- apiSecret = reqQuery.apiSecret || results[1],
+ var apiKey = (typeof reqQuery.apiKey == "undefined" || reqQuery.apiKey == "undefined")?results[0]:reqQuery.apiKey,
+ apiSecret = (typeof reqQuery.apiSecret == "undefined" || reqQuery.apiSecret == "undefined")?results[1]:reqQuery.apiSecret,
accessToken = results[2],
accessTokenSecret = results[3];
-
+ console.log(apiKey);
+ console.log(apiSecret);
+ console.log(accessToken);
+ console.log(accessTokenSecret);
+
var oa = new OAuth(apiConfig.oauth.requestURL || null,
apiConfig.oauth.accessURL || null,
apiKey || null,
@@ -477,7 +485,13 @@ function processRequest(req, res, next) {
// Add API Key to params, if any.
if (apiKey != '' && apiKey != 'undefined' && apiKey != undefined) {
- options.path += '&' + apiConfig.keyParam + '=' + apiKey;
+ if (options.path.indexOf('?') !== -1) {
+ options.path += '&';
+ }
+ else {
+ options.path += '?';
+ }
+ options.path += apiConfig.keyParam + '=' + apiKey;
}
// Perform signature routine, if any.
@@ -485,13 +499,13 @@ function processRequest(req, res, next) {
if (apiConfig.signature.type == 'signed_md5') {
// Add signature parameter
var timeStamp = Math.round(new Date().getTime()/1000);
- var sig = hashlib.md5('' + apiKey + apiSecret + timeStamp + '', { asString: true });
+ var sig = crypto.createHash('md5').update('' + apiKey + apiSecret + timeStamp + '').digest(apiConfig.signature.digest);
options.path += '&' + apiConfig.signature.sigParam + '=' + sig;
}
else if (apiConfig.signature.type == 'signed_sha256') { // sha256(key+secret+epoch)
// Add signature parameter
var timeStamp = Math.round(new Date().getTime()/1000);
- var sig = hashlib.sha256('' + apiKey + apiSecret + timeStamp + '', { asString: true });
+ var sig = crypto.createHash('sha256').update('' + apiKey + apiSecret + timeStamp + '').digest(apiConfig.signature.digest);
options.path += '&' + apiConfig.signature.sigParam + '=' + sig;
}
}
@@ -601,6 +615,21 @@ function processRequest(req, res, next) {
// Passes variables to the view
app.dynamicHelpers({
session: function(req, res) {
+ // If api wasn't passed in as a parameter, check the path to see if it's there
+ if (!req.params.api) {
+ pathName = req.url.replace('/','');
+ // Is it a valid API - if there's a config file we can assume so
+ fs.stat('public/data/' + pathName + '.json', function (error, stats) {
+ if (stats) {
+ req.params.api = pathName;
+ }
+ });
+ }
+ // If the cookie says we're authed for this particular API, set the session to authed as well
+ if (req.params.api && req.session[req.params.api] && req.session[req.params.api]['authed']) {
+ req.session['authed'] = true;
+ }
+
return req.session;
},
apiInfo: function(req, res) {
@@ -666,6 +695,7 @@ app.get('/:api([^\.]+)', function(req, res) {
// Only listen on $ node app.js
if (!module.parent) {
- app.listen(config.port, config.address);
+ var port = process.env.PORT || config.port;
+ app.listen(port);
console.log("Express server listening on port %d", app.address().port);
}
9 package.json
View
@@ -22,16 +22,15 @@
},
"dependencies": {
"connect-redis": "1.0.6",
- "express": "2.4.3",
- "hashlib": "1.0.1",
+ "express": "2.5.8",
"jade": "0.13",
"oauth": "0.9.3",
- "querystring": "0.0.1",
- "redis": "0.6.6"
+ "redis": "0.6.6",
+ "querystring": "0.1.0"
},
"devDependencies": {},
"main": "index",
"engines": {
"node": ">= 0.4.0"
}
-}
+}
157 public/data/alibris.json
View
@@ -1,157 +0,0 @@
-{
- "endpoints":[
- {
- "name":"All API Methods",
- "methods":[
- {
- "MethodName":"search",
- "Synopsis":"Returns summary level records based on search criteria",
- "HTTPMethod":"GET",
- "URI":"/search",
- "RequiresOAuth":"N",
- "parameters":[
- {
- "Name":"wauth",
- "Required":"N",
- "Default":"",
- "Type":"string",
- "Description":"Search by author (AUTHOR)"
- },
- {
- "Name":"wtit",
- "Required":"N",
- "Default":"",
- "Type":"string",
- "Description":"Search by title (TITLE)"
- },
- {
- "Name":"wtopic",
- "Required":"N",
- "Default":"",
- "Type":"string",
- "Description":"Search by topic (BASIC, LC_SUBJECT, GEO_CODE)"
- },
- {
- "Name":"wquery",
- "Required":"N",
- "Default":"",
- "Type":"string",
- "Description":"Search by query (AUTHOR, TITLE, BASIC, LC_SUBJECT, GEO_CODE)"
- },
- {
- "Name":"mtype",
- "Required":"N",
- "Default":"B",
- "Type":"enumerated",
- "EnumeratedList": [
- "B",
- "M",
- "V",
- "A"
- ],
- "Description":"Media type (A=all, B=books, M=music, V=movies)"
- },
- {
- "Name":"chunk",
- "Required":"N",
- "Default":"25",
- "Type":"integer",
- "Description":"Number of results returned"
- },
- {
- "Name":"skip",
- "Required":"N",
- "Default":"",
- "Type":"integer",
- "Description":"Number of results to skip"
- },
- {
- "Name":"qsort",
- "Required":"N",
- "Default":"r",
- "Type":"enumerated",
- "EnumeratedList": [
- "r",
- "t",
- "tr",
- "a",
- "ar",
- "p",
- "pr",
- "d",
- "dr"
- ],
- "Description":"Sorting method - r=rating/price (books only), t=title, tr=title reverse, a=author, ar=author reverse, p=price, pr=price reverse, d=date (year), dr=date reverse"
- },
- {
- "Name":"outputtype",
- "Required":"N",
- "Default":"json",
- "Type":"enumerated",
- "EnumeratedList": [
- "xml",
- "json"
- ],
- "Description":"Results format"
- }
- ]
- },
- {
- "MethodName":"recommend",
- "Synopsis":"Returns 6 randomized recommendations based on given WORK_ID",
- "HTTPMethod":"GET",
- "URI":"/recommend",
- "RequiresOAuth":"N",
- "parameters":[
- {
- "Name":"work",
- "Required":"Y",
- "Default":"",
- "Type":"string",
- "Description":"WORK_ID to base recommendations upon"
- },
- {
- "Name":"outputtype",
- "Required":"N",
- "Default":"json",
- "Type":"enumerated",
- "EnumeratedList": [
- "xml",
- "json"
- ],
- "Description":"Results format"
- }
- ]
- },
- {
- "MethodName":"review",
- "Synopsis":"Returns 10 most recent Alibris reviews for given WORK_ID",
- "HTTPMethod":"GET",
- "URI":"/review",
- "RequiresOAuth":"N",
- "parameters":[
- {
- "Name":"work",
- "Required":"Y",
- "Default":"",
- "Type":"string",
- "Description":"WORK_ID to base review search upon"
- },
- {
- "Name":"outputtype",
- "Required":"N",
- "Default":"json",
- "Type":"enumerated",
- "EnumeratedList": [
- "xml",
- "json"
- ],
- "Description":"Results format"
- }
- ]
- }
- ]
- }
- ]
-}
-
25 public/data/apiconfig.json
View
@@ -15,14 +15,6 @@
"auth": "key",
"keyParam": "api_key"
},
- "alibris": {
- "name": "Alibris API",
- "protocol": "http",
- "baseURL": "api.alibris.com",
- "publicPath": "/v1/public",
- "auth": "key",
- "keyParam": "apikey"
- },
"usatoday": {
"name": "USA TODAY Census API",
"protocol": "http",
@@ -31,6 +23,23 @@
"auth": "key",
"keyParam": "api_key"
},
+ "linkedin": {
+ "name": "LinkedIn",
+ "protocol": "http",
+ "baseURL": "api.linkedin.com",
+ "publicPath": "",
+ "privatePath": "/v1",
+ "auth": "oauth",
+ "oauth": {
+ "type": "three-legged",
+ "requestURL": "https://api.linkedin.com/uas/oauth/requestToken",
+ "signinURL": "https://api.linkedin.com/uas/oauth/authorize?oauth_token=",
+ "accessURL": "https://api.linkedin.com/uas/oauth/accessToken",
+ "version": "1.0",
+ "crypt": "HMAC-SHA1"
+ },
+ "keyParam":""
+ },
"simplegeo": {
"name": "SimpleGeo",
"protocol": "http",
4,129 public/data/linkedin.json
View
4,129 additions, 0 deletions not shown
Please sign in to comment.
Something went wrong with that request. Please try again.