Skip to content

Commit

Permalink
refactor to a provider/framework type absraction
Browse files Browse the repository at this point in the history
  • Loading branch information
dougmoscrop committed Apr 17, 2019
1 parent e614c64 commit e586231
Show file tree
Hide file tree
Showing 28 changed files with 310 additions and 298 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

[![Build Status](https://travis-ci.org/dougmoscrop/serverless-http.svg?branch=master)](https://travis-ci.org/dougmoscrop/serverless-http)

## 2.0

The 2.0 release moves to async/await and requires Node 8+.

All frameworks have been updated to their latest versions.

## Description

This module allows you to 'wrap' your API for serverless use. No HTTP server, no ports or sockets. Just your code in the same execution pipeline you are already familiar with.
Expand Down Expand Up @@ -45,9 +51,13 @@ module.exports.handler = serverless(app);
// or as a promise
const handler = serverless(app);
module.exports.handler = async (event, context) => {
return await handler(event, context);
// you can do other things here
const result = await handler(event, context);
// and here
return result;
};
```

### Other examples
[json-server-less-λ](https://github.com/pharindoko/json-server-less-lambda) - using serverless-http with json-server and serverless framework in AWS

Expand Down
19 changes: 8 additions & 11 deletions docs/ADVANCED.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,28 @@ Some examples:

```javascript
module.exports.handler = serverless(app, {

request: {
key: 'value'
},
response: function(res) {
return Promise.resolve()
.then(() => {
res.foo = 'bar';
});
response(res) {
res.foo = 'bar';
}
})

module.exports.handler = serverless(app, {
request: function(request, event, context) {
request(request, event, context) {
request.context = event.requestContext;
},
response: function(response, event, context) {
// you can return promises, but the value of the promise is ignored
async response(response, event, context) {
// the return value is always ignored
return new Promise(resolve => {
// override a property of the response, this will affect the response
response.statusCode = 420;

// delay your responses by 300ms!
setTimeout(300, () => {
resolve('banana'); // this value has no effect on the response
// this value has no effect on the response
resolve('banana');
});
});
}
Expand Down Expand Up @@ -80,7 +77,7 @@ serverless(app, {

// your own custom callback
serverless(app, {
binary: function(headers) {
binary(headers) {
return ...
}
});
Expand Down
4 changes: 2 additions & 2 deletions docs/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ service: hello-world

provider:
name: aws
runtime: nodejs4.3
runtime: nodejs8.10

functions:
api:
Expand All @@ -38,7 +38,7 @@ service: hello-world
provider:
name: aws
runtime: nodejs4.3
runtime: nodejs8.10
functions:
foo_api:
Expand Down
45 changes: 16 additions & 29 deletions lib/finish.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
'use strict';

module.exports = function finish(item, event, context, transform) {
return new Promise((res, reject) => {
// TODO: is this important?
const resolve = value => {
process.nextTick(() => {
res(value);
});
};

module.exports = async function finish(item, transform, ...details) {
await new Promise((resolve, reject) => {
if (item.finished || item.complete) {
resolve();
return;
Expand All @@ -23,31 +16,25 @@ module.exports = function finish(item, event, context, transform) {

finished = true;

item.removeListener('finish', done);
item.removeListener('done', done);

if (err) {
reject(err);
} else {
resolve();
}
}

item.once('end', (err) => {
item.removeListener('finish', done);
done(err);
});

item.once('finish', (err) => {
item.removeListener('done', done);
done(err);
});
})
.then(() => {
if (typeof transform === 'function') {
return transform(item, event, context);
} else if (typeof transform === 'object' && transform !== null) {
Object.assign(item, transform);
}
})
.then(() => {
return item;
item.once('end', done);
item.once('finish', done);
});
}

if (typeof transform === 'function') {
await transform(item, ...details);
} else if (typeof transform === 'object' && transform !== null) {
Object.assign(item, transform);
}

return item;
};
54 changes: 54 additions & 0 deletions lib/framework/get-framework.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use strict';

const Response = require('../response');

function common(cb) {
return request => {
const response = new Response(request);

cb(request, response);

return response;
};
}

module.exports = function getFramework(app) {
if (typeof app.callback === 'function') {
return common(app.callback());
}

if (typeof app.handle === 'function') {
return common((request, response) => {
app.handle(request, response);
});
}

if (typeof app === 'function') {
return common(app);
}

if (app.router && typeof app.router.route == 'function') {
return common((req, res) => {
const { url, method, headers, body } = req;
app.router.route({ url, method, headers, body }, res);
});
}

if (app._core && typeof app._core._dispatch === 'function') {
return common(app._core._dispatch({
app
}));
}

if (typeof app.inject === 'function') {
return async request => {
const { method, url, headers, body } = request;

const res = await app.inject({ method, url, headers, payload: body })

return Response.from(res);
};
}

throw new Error('Unsupported framework');
};
15 changes: 0 additions & 15 deletions lib/get-body.js

This file was deleted.

80 changes: 0 additions & 80 deletions lib/get-handler.js

This file was deleted.

11 changes: 0 additions & 11 deletions lib/get-string.js

This file was deleted.

File renamed without changes.
45 changes: 45 additions & 0 deletions lib/provider/aws/create-request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict';

const Request = require('../../request');

function requestHeaders(event) {
return Object.keys(event.headers).reduce((headers, key) => {
headers[key.toLowerCase()] = event.headers[key];
return headers;
}, {});
}

function requestBody(event, headers) {
if (typeof event.body === 'string') {
return Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8');
} else if (Buffer.isBuffer(event.body)) {
return event.body;
} else if (typeof event.body === 'object') {
const contentType = headers['content-type'];

if (contentType && contentType.indexOf('application/json') === 0) {
return Buffer.from(JSON.stringify(event.body));
}

throw new Error('event.body was an object but content-type is not json');
}

throw new Error(`Unexpected event.body type: ${typeof event.body}`);
}

module.exports = (event, options) => {
const path = event.requestContext.path;
const method = event.httpMethod;
const query = event.multiValueQueryStringParameters || event.queryStringParameters;
const baseUrl = event.requestContext.path.slice(0, -event.path.length);
const remoteAddress = event.requestContext.identity.sourceIp;
const headers = requestHeaders(event);
const body = requestBody(event, headers);

if (typeof options.requestId === 'string' && options.requestId.length > 0) {
const header = options.requestId.toLowerCase();
headers[header] = headers[header] || event.requestContext.requestId;
}

return new Request({ method, path, baseUrl, query, headers, body, remoteAddress });
};
20 changes: 20 additions & 0 deletions lib/provider/aws/format-response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

const isBinary = require('./is-binary');
const Response = require('../../response');
const sanitizeHeaders = require('./sanitize-headers');

module.exports = (response, options) => {
const { statusCode } = response;
const headers = sanitizeHeaders(Response.headers(response));

if (headers['transfer-encoding'] === 'chunked' || response.chunkedEncoding) {
throw new Error('chunked encoding not supported');
}

const isBase64Encoded = isBinary(headers, options);
const encoding = isBase64Encoded ? 'base64' : 'utf8';
const body = Response.body(response).toString(encoding);

return { statusCode, headers, isBase64Encoded, body };
};
Loading

0 comments on commit e586231

Please sign in to comment.