README
Small Orange Gateway
Simple HTTP gateway for lambdas
This gateway takes care to create a HTTP server, call lambda functions, cache into Redis according to provided strategy and log into cloudWatch.
Sample
Setup
// used env vars
process.env.ACCESS_KEY_ID = 'xxxxx'; // (required)
process.env.SECRET_ACCESS_KEY = 'xxxxx'; // (required)
process.env.REGION = 'xxxxx'; // (optional)
process.env.REDIS_URL = 'xxxxx'; // (optional)
process.env.LOG_GROUP = 'xxxxx'; // (optional)
process.env.PORT = 8080; // (optional)
process.env.CACHE_PREFIX = ''; // (optional)
process.env.CACHE_TTL = 2592000; // time in seconds to live (optional) default: 30 days
process.env.CACHE_TTR = 7200; // time in seconds to refresh (optional) default: 2 hours
process.env.CACHE_TIMEOUT = 1000; // time in ms to wait before route to the origin (optional) default: 1 second
// lambdas manifest
const lambdas = {
'/': {
name: 'functionName' // required,
cache: {
driver: {}, // Object like https://github.com/feliperohdee/smallorange-rxjs-cache-driver
enabled: args => args.method === 'GET' && !args.hasExtension && !args.url.query || boolean,
namespace: args => args.host, // required
key: args => args.url.pathname || string // required,
options: args => ({
ttl: number, // optional, override default
ttr: number // optional, override default
})
},
transform: {
args: args => { // it happens immediately after authentication is handled
args.headers = {
...args.headers,
customHeader: 'customHeaderValue'
};
},
response: (response, req, res) => {
response.body = ...;
return response;
},
error: err => {
return err;
}
}
},
// or with HTTP verb
'POST /': {
name: 'functionName' // required,
cache: {
enabled: args => args.method === 'GET' && !args.hasExtension && !args.url.query || boolean,
namespace: args => args.host, // required
key: args => args.url.pathname || string // required
},
transform: {
args: args => {
args.headers = {
...args.headers,
customHeader: 'customHeaderValue'
};
}
}
},
'/functionName': {
name: 'functionName', // required
// pass just params (not all args as described below) to the lambda function
paramsOnly: true,
defaults: {
// default request params, it will be merged with params fetched from req.query, in case of key collision, the latter is going to have precedence
requestParams: {
width: 100,
height: 100
},
// default response base64 value, lambda response can override this value, if checked, value will be converted to a buffer before returns to the browser
responseBase64: true,
// default response headers, lambda response headers will be merged with this value, in case of key collision, the latter is going to have precedence
respondeHeaders: {
'content-type': 'image/png'
}
}
},
'/local': {
name: 'functionName', // required,
local: path.resolve('...path to local function index')
},
'/mocked': {
mocked: args => 'anything'
},
'/authOnly': {
name: 'functionName' // required,
auth: {
enabled: true,
allowedFields: ['role', 'user', 'loggedAt'], // (optional)
handleInvalidSignature: false, // (optional)
secret: (payload, params, headers) => 'mySecret' || 'mySecret', // (required)
token(params, headers) => params.token || headers.authorization // (optional),
options: {
/*
algorithms: List of strings with the names of the allowed algorithms. For instance, ["HS256", "HS384"].
audience: if you want to check audience (aud), provide a value here
issuer (optional): string or array of strings of valid values for the iss field.
ignoreExpiration: if true do not validate the expiration of the token.
ignoreNotBefore...
subject: if you want to check subject (sub), provide a value here
clockTolerance: number of seconds to tolerate when checking the nbf and exp claims, to deal with small clock differences among different servers
*/
},
resolve: (auth, args) => object || Observable<Object> // (optional)
}
},
'/adminOnly': {
enabled: args => true,
name: 'functionName' // required,
auth: {
// ...
requiredRoles: ['admin']
}
},
'/adminOrPublic': {
name: 'functionName' // required,
auth: {
// ...
requiredRoles: ['admin', 'public']
}
},
// note: JWT should have role property, like:
// {
// role: string, // (required)
// ...anyOtherParams
// }
// full wildcards
'/*': {
name: 'functionName' // required,
},
'/*/*': {
name: 'functionName' // required,
},
// partial wildcards
'/*/functionName': {
name: 'functionName' // required,
},
'/*/*/functionName': {
name: 'functionName' // required,
}
};
const gateway = new Gateway({
logGroup: 'myAppLogs', // || env.LOG_GROUP
lambdas,
redisUrl: 'redis://localhost:6380', // || env.REDIS_URL
cachePrefix: '', || // env.CACHE_PREFIX
});
Usage Details
// for a request like
GET http://localhost/functionName/resource?string=value&number=2&boolean=true&nulled=null
// lambda function will receive args like:
{
auth: {}, // if enabled
body: {},
hasExtension: false, // if url ends with .jpg or .png
headers: {
//...request headers
},
host: 'http://localhost',
method: 'GET',
// fetched from req.query
params: {
string: 'value',
number: 2,
boolean: true,
nulled: null,
},
url: {
path: '/functionName/resource?string=value&number=2&boolean=true&nulled=null',
pathname: '/functionName/resource',
query: 'string=value&number=2&boolean=true&nulled=null'
},
uri: '/functionName/resource'
}
// or just params if explicity declared at lambdas manifest with "paramsOnly = true":
{
string: 'value',
number: 2,
boolean: true,
nulled: null
}
// lambdas can responds with just string, or an object with following signature
{
//string or stringified object,
body: string,
headers: object,
base64: boolean,
statusCode: number // is statusCode >= 400, gateway is going to handle as an error following the Http/1.1 rfc (https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
}
Cache handling
// you can manually mark cache to refresh making a request like:
POST http://yourhost/cache
{
operation: 'markToRefresh',
namespace: 'http://localhost'
}
// or unset
POST http://yourhost/cache
{
operation: 'unset',
namespace: 'http://localhost',
keys: ['/', '/cart']
}