Skip to content

Commit

Permalink
feat: add option of specifying resolveMode (CodeGenieApp#173)
Browse files Browse the repository at this point in the history
* feat: add option of specifying resolveMode

Allows resolving using Promise, callback, or the default context.succeed.

API decisions were made to support backwards compatibility. We will clean up the interface in a future major version bump
  • Loading branch information
brettstack authored Aug 16, 2018
1 parent fca195d commit 582b88d
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 37 deletions.
54 changes: 52 additions & 2 deletions __tests__/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const app = require('../examples/basic-starter/app')

const server = awsServerlessExpress.createServer(app)
const lambdaFunction = {
handler: (event, context) => awsServerlessExpress.proxy(server, event, context)
handler: (event, context, resolutionMode, callback) => awsServerlessExpress.proxy(server, event, context, resolutionMode, callback)
}

function clone (json) {
Expand Down Expand Up @@ -50,6 +50,20 @@ function makeResponse (response) {
}

describe('integration tests', () => {
test('proxy returns server', (done) => {
const succeed = () => {
done()
}

const server = lambdaFunction.handler(makeEvent({
path: '/',
httpMethod: 'GET'
}), {
succeed
})
expect(server._socketPathSuffix).toBeTruthy()
})

test('GET HTML (initial request)', (done) => {
const succeed = response => {
delete response.headers.date
Expand Down Expand Up @@ -82,7 +96,6 @@ describe('integration tests', () => {
succeed
})
})

test('GET JSON collection', (done) => {
const succeed = response => {
delete response.headers.date
Expand Down Expand Up @@ -149,6 +162,43 @@ describe('integration tests', () => {
})
})

test('GET JSON single (resolutionMode = CALLBACK)', (done) => {
const callback = (e, response) => {
delete response.headers.date
expect(response).toEqual(makeResponse({
'body': '{"id":1,"name":"Joe"}',
'headers': {
'content-length': '21',
'etag': 'W/"15-rRboW+j/yFKqYqV6yklp53+fANQ"'
}
}))
done()
}
lambdaFunction.handler(makeEvent({
path: '/users/1',
httpMethod: 'GET'
}), {}, 'CALLBACK', callback)
})

test('GET JSON single (resolutionMode = PROMISE)', (done) => {
const succeed = response => {
delete response.headers.date
expect(response).toEqual(makeResponse({
'body': '{"id":1,"name":"Joe"}',
'headers': {
'content-length': '21',
'etag': 'W/"15-rRboW+j/yFKqYqV6yklp53+fANQ"'
}
}))
done()
}
lambdaFunction.handler(makeEvent({
path: '/users/1',
httpMethod: 'GET'
}), {}, 'PROMISE')
.promise.then(succeed)
})

test('GET JSON single 404', (done) => {
const succeed = response => {
delete response.headers.date
Expand Down
123 changes: 105 additions & 18 deletions __tests__/unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,50 @@ class MockContext {
}
}

describe('forwardConnectionErrorResponseToApiGateway', () => {
test('responds with 502 status', () => {
return new Promise(
(resolve, reject) => {
const context = new MockContext(resolve)
const contextResolver = {
succeed: (p) => context.succeed(p.response)
}
awsServerlessExpress.forwardConnectionErrorResponseToApiGateway('ERROR', contextResolver)
}
).then(successResponse => expect(successResponse).toEqual({
statusCode: 502,
body: '',
headers: {}
}))
})
})

describe('forwardLibraryErrorResponseToApiGateway', () => {
test('responds with 500 status', () => {
return new Promise(
(resolve, reject) => {
const context = new MockContext(resolve)
const contextResolver = {
succeed: (p) => context.succeed(p.response)
}
awsServerlessExpress.forwardLibraryErrorResponseToApiGateway('ERROR', contextResolver)
}
).then(successResponse => expect(successResponse).toEqual({
statusCode: 500,
body: '',
headers: {}
}))
})
})

function getContextResolver (resolve) {
const context = new MockContext(resolve)
const contextResolver = {
succeed: (p) => context.succeed(p.response)
}

return contextResolver
}
describe('forwardResponseToApiGateway: header handling', () => {
test('multiple headers with the same name get transformed', () => {
const server = new MockServer()
Expand All @@ -135,9 +179,8 @@ describe('forwardResponseToApiGateway: header handling', () => {
const response = new MockResponse(200, headers, body)
return new Promise(
(resolve, reject) => {
const context = new MockContext(resolve)
awsServerlessExpress.forwardResponseToApiGateway(
server, response, context)
const contextResolver = getContextResolver(resolve)
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
}
).then(successResponse => expect(successResponse).toEqual({
statusCode: 200,
Expand All @@ -156,9 +199,8 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
const response = new MockResponse(200, headers, body)
return new Promise(
(resolve, reject) => {
const context = new MockContext(resolve)
awsServerlessExpress.forwardResponseToApiGateway(
server, response, context)
const contextResolver = getContextResolver(resolve)
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
}
).then(successResponse => expect(successResponse).toEqual({
statusCode: 200,
Expand All @@ -175,9 +217,8 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
const response = new MockResponse(200, headers, body)
return new Promise(
(resolve, reject) => {
const context = new MockContext(resolve)
awsServerlessExpress.forwardResponseToApiGateway(
server, response, context)
const contextResolver = getContextResolver(resolve)
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
}
).then(successResponse => expect(successResponse).toEqual({
statusCode: 200,
Expand All @@ -194,9 +235,8 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
const response = new MockResponse(200, headers, body)
return new Promise(
(resolve, reject) => {
const context = new MockContext(resolve)
awsServerlessExpress.forwardResponseToApiGateway(
server, response, context)
const contextResolver = getContextResolver(resolve)
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
}
).then(successResponse => expect(successResponse).toEqual({
statusCode: 200,
Expand All @@ -213,9 +253,8 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
const response = new MockResponse(200, headers, body)
return new Promise(
(resolve, reject) => {
const context = new MockContext(resolve)
awsServerlessExpress.forwardResponseToApiGateway(
server, response, context)
const contextResolver = getContextResolver(resolve)
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
}
).then(successResponse => expect(successResponse).toEqual({
statusCode: 200,
Expand All @@ -232,9 +271,8 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
const response = new MockResponse(200, headers, body)
return new Promise(
(resolve, reject) => {
const context = new MockContext(resolve)
awsServerlessExpress.forwardResponseToApiGateway(
server, response, context)
const contextResolver = getContextResolver(resolve)
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
}
).then(successResponse => expect(successResponse).toEqual({
statusCode: 200,
Expand All @@ -244,3 +282,52 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
}))
})
})

describe('makeResolver', () => {
test('CONTEXT_SUCCEED (specified)', () => {
return new Promise(
(resolve, reject) => {
const context = new MockContext(resolve)
const contextResolver = awsServerlessExpress.makeResolver({
context,
resolutionMode: 'CONTEXT_SUCCEED'
})

return contextResolver.succeed({
response: 'success'
})
}).then(successResponse => expect(successResponse).toEqual('success'))
})

test('CALLBACK', () => {
const callback = (e, response) => response
const callbackResolver = awsServerlessExpress.makeResolver({
callback,
resolutionMode: 'CALLBACK'
})
const successResponse = callbackResolver.succeed({
response: 'success'
})

expect(successResponse).toEqual('success')
})

test('PROMISE', () => {
return new Promise((resolve, reject) => {
const promise = {
resolve,
reject
}
const promiseResolver = awsServerlessExpress.makeResolver({
promise,
resolutionMode: 'PROMISE'
})

return promiseResolver.succeed({
response: 'success'
})
}).then(successResponse => {
expect(successResponse).toEqual('success')
})
})
})
78 changes: 61 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function mapApiGatewayEventToHttpRequest (event, context, socketPath) {
}
}

function forwardResponseToApiGateway (server, response, context) {
function forwardResponseToApiGateway (server, response, resolver) {
let buf = []

response
Expand Down Expand Up @@ -88,11 +88,11 @@ function forwardResponseToApiGateway (server, response, context) {
const body = bodyBuffer.toString(isBase64Encoded ? 'base64' : 'utf8')
const successResponse = {statusCode, body, headers, isBase64Encoded}

context.succeed(successResponse)
resolver.succeed({ response: successResponse })
})
}

function forwardConnectionErrorResponseToApiGateway (server, error, context) {
function forwardConnectionErrorResponseToApiGateway (error, resolver) {
console.log('ERROR: aws-serverless-express connection error')
console.error(error)
const errorResponse = {
Expand All @@ -101,10 +101,10 @@ function forwardConnectionErrorResponseToApiGateway (server, error, context) {
headers: {}
}

context.succeed(errorResponse)
resolver.succeed({ response: errorResponse })
}

function forwardLibraryErrorResponseToApiGateway (server, error, context) {
function forwardLibraryErrorResponseToApiGateway (error, resolver) {
console.log('ERROR: aws-serverless-express error')
console.error(error)
const errorResponse = {
Expand All @@ -113,13 +113,13 @@ function forwardLibraryErrorResponseToApiGateway (server, error, context) {
headers: {}
}

context.succeed(errorResponse)
resolver.succeed({ response: errorResponse })
}

function forwardRequestToNodeServer (server, event, context) {
function forwardRequestToNodeServer (server, event, context, resolver) {
try {
const requestOptions = mapApiGatewayEventToHttpRequest(event, context, getSocketPath(server._socketPathSuffix))
const req = http.request(requestOptions, (response, body) => forwardResponseToApiGateway(server, response, context))
const req = http.request(requestOptions, (response) => forwardResponseToApiGateway(server, response, resolver))
if (event.body) {
if (event.isBase64Encoded) {
event.body = Buffer.from(event.body, 'base64')
Expand All @@ -128,10 +128,10 @@ function forwardRequestToNodeServer (server, event, context) {
req.write(event.body)
}

req.on('error', (error) => forwardConnectionErrorResponseToApiGateway(server, error, context))
req.on('error', (error) => forwardConnectionErrorResponseToApiGateway(error, resolver))
.end()
} catch (error) {
forwardLibraryErrorResponseToApiGateway(server, error, context)
forwardLibraryErrorResponseToApiGateway(error, resolver)
return server
}
}
Expand Down Expand Up @@ -182,13 +182,56 @@ function createServer (requestListener, serverListenCallback, binaryTypes) {
return server
}

function proxy (server, event, context) {
if (server._isListening) {
forwardRequestToNodeServer(server, event, context)
return server
} else {
return startServer(server)
.on('listening', () => proxy(server, event, context))
function proxy (server, event, context, resolutionMode, callback) {
// DEPRECATED: Legacy support
if (!resolutionMode) {
const resolver = makeResolver({ context, resolutionMode: 'CONTEXT_SUCCEED' })
if (server._isListening) {
forwardRequestToNodeServer(server, event, context, resolver)
return server
} else {
return startServer(server)
.on('listening', () => proxy(server, event, context))
}
}

return {
promise: new Promise((resolve, reject) => {
const promise = {
resolve,
reject
}
const resolver = makeResolver({
context,
callback,
promise,
resolutionMode
})

if (server._isListening) {
forwardRequestToNodeServer(server, event, context, resolver)
} else {
startServer(server)
.on('listening', () => forwardRequestToNodeServer(server, event, context, resolver))
}
})
}
}

function makeResolver (params/* {
context,
callback,
promise,
resolutionMode
} */) {
return {
succeed: (params2/* {
response
} */) => {
if (params.resolutionMode === 'CONTEXT_SUCCEED') return params.context.succeed(params2.response)
if (params.resolutionMode === 'CALLBACK') return params.callback(null, params2.response)
if (params.resolutionMode === 'PROMISE') return params.promise.resolve(params2.response)
}
}
}

Expand All @@ -205,4 +248,5 @@ if (process.env.NODE_ENV === 'test') {
exports.forwardRequestToNodeServer = forwardRequestToNodeServer
exports.startServer = startServer
exports.getSocketPath = getSocketPath
exports.makeResolver = makeResolver
}

0 comments on commit 582b88d

Please sign in to comment.