Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add option of specifying resolveMode #173

Merged
merged 3 commits into from
Aug 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -122,6 +122,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 @@ -130,9 +174,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 @@ -151,9 +194,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 @@ -170,9 +212,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 @@ -189,9 +230,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 @@ -208,9 +248,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 @@ -227,9 +266,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 @@ -239,3 +277,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
}