Skip to content

Commit 582b88d

Browse files
authored
feat: add option of specifying resolveMode (#173)
* 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
1 parent fca195d commit 582b88d

File tree

3 files changed

+218
-37
lines changed

3 files changed

+218
-37
lines changed

__tests__/integration.js

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const app = require('../examples/basic-starter/app')
66

77
const server = awsServerlessExpress.createServer(app)
88
const lambdaFunction = {
9-
handler: (event, context) => awsServerlessExpress.proxy(server, event, context)
9+
handler: (event, context, resolutionMode, callback) => awsServerlessExpress.proxy(server, event, context, resolutionMode, callback)
1010
}
1111

1212
function clone (json) {
@@ -50,6 +50,20 @@ function makeResponse (response) {
5050
}
5151

5252
describe('integration tests', () => {
53+
test('proxy returns server', (done) => {
54+
const succeed = () => {
55+
done()
56+
}
57+
58+
const server = lambdaFunction.handler(makeEvent({
59+
path: '/',
60+
httpMethod: 'GET'
61+
}), {
62+
succeed
63+
})
64+
expect(server._socketPathSuffix).toBeTruthy()
65+
})
66+
5367
test('GET HTML (initial request)', (done) => {
5468
const succeed = response => {
5569
delete response.headers.date
@@ -82,7 +96,6 @@ describe('integration tests', () => {
8296
succeed
8397
})
8498
})
85-
8699
test('GET JSON collection', (done) => {
87100
const succeed = response => {
88101
delete response.headers.date
@@ -149,6 +162,43 @@ describe('integration tests', () => {
149162
})
150163
})
151164

165+
test('GET JSON single (resolutionMode = CALLBACK)', (done) => {
166+
const callback = (e, response) => {
167+
delete response.headers.date
168+
expect(response).toEqual(makeResponse({
169+
'body': '{"id":1,"name":"Joe"}',
170+
'headers': {
171+
'content-length': '21',
172+
'etag': 'W/"15-rRboW+j/yFKqYqV6yklp53+fANQ"'
173+
}
174+
}))
175+
done()
176+
}
177+
lambdaFunction.handler(makeEvent({
178+
path: '/users/1',
179+
httpMethod: 'GET'
180+
}), {}, 'CALLBACK', callback)
181+
})
182+
183+
test('GET JSON single (resolutionMode = PROMISE)', (done) => {
184+
const succeed = response => {
185+
delete response.headers.date
186+
expect(response).toEqual(makeResponse({
187+
'body': '{"id":1,"name":"Joe"}',
188+
'headers': {
189+
'content-length': '21',
190+
'etag': 'W/"15-rRboW+j/yFKqYqV6yklp53+fANQ"'
191+
}
192+
}))
193+
done()
194+
}
195+
lambdaFunction.handler(makeEvent({
196+
path: '/users/1',
197+
httpMethod: 'GET'
198+
}), {}, 'PROMISE')
199+
.promise.then(succeed)
200+
})
201+
152202
test('GET JSON single 404', (done) => {
153203
const succeed = response => {
154204
delete response.headers.date

__tests__/unit.js

Lines changed: 105 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,50 @@ class MockContext {
127127
}
128128
}
129129

130+
describe('forwardConnectionErrorResponseToApiGateway', () => {
131+
test('responds with 502 status', () => {
132+
return new Promise(
133+
(resolve, reject) => {
134+
const context = new MockContext(resolve)
135+
const contextResolver = {
136+
succeed: (p) => context.succeed(p.response)
137+
}
138+
awsServerlessExpress.forwardConnectionErrorResponseToApiGateway('ERROR', contextResolver)
139+
}
140+
).then(successResponse => expect(successResponse).toEqual({
141+
statusCode: 502,
142+
body: '',
143+
headers: {}
144+
}))
145+
})
146+
})
147+
148+
describe('forwardLibraryErrorResponseToApiGateway', () => {
149+
test('responds with 500 status', () => {
150+
return new Promise(
151+
(resolve, reject) => {
152+
const context = new MockContext(resolve)
153+
const contextResolver = {
154+
succeed: (p) => context.succeed(p.response)
155+
}
156+
awsServerlessExpress.forwardLibraryErrorResponseToApiGateway('ERROR', contextResolver)
157+
}
158+
).then(successResponse => expect(successResponse).toEqual({
159+
statusCode: 500,
160+
body: '',
161+
headers: {}
162+
}))
163+
})
164+
})
165+
166+
function getContextResolver (resolve) {
167+
const context = new MockContext(resolve)
168+
const contextResolver = {
169+
succeed: (p) => context.succeed(p.response)
170+
}
171+
172+
return contextResolver
173+
}
130174
describe('forwardResponseToApiGateway: header handling', () => {
131175
test('multiple headers with the same name get transformed', () => {
132176
const server = new MockServer()
@@ -135,9 +179,8 @@ describe('forwardResponseToApiGateway: header handling', () => {
135179
const response = new MockResponse(200, headers, body)
136180
return new Promise(
137181
(resolve, reject) => {
138-
const context = new MockContext(resolve)
139-
awsServerlessExpress.forwardResponseToApiGateway(
140-
server, response, context)
182+
const contextResolver = getContextResolver(resolve)
183+
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
141184
}
142185
).then(successResponse => expect(successResponse).toEqual({
143186
statusCode: 200,
@@ -156,9 +199,8 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
156199
const response = new MockResponse(200, headers, body)
157200
return new Promise(
158201
(resolve, reject) => {
159-
const context = new MockContext(resolve)
160-
awsServerlessExpress.forwardResponseToApiGateway(
161-
server, response, context)
202+
const contextResolver = getContextResolver(resolve)
203+
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
162204
}
163205
).then(successResponse => expect(successResponse).toEqual({
164206
statusCode: 200,
@@ -175,9 +217,8 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
175217
const response = new MockResponse(200, headers, body)
176218
return new Promise(
177219
(resolve, reject) => {
178-
const context = new MockContext(resolve)
179-
awsServerlessExpress.forwardResponseToApiGateway(
180-
server, response, context)
220+
const contextResolver = getContextResolver(resolve)
221+
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
181222
}
182223
).then(successResponse => expect(successResponse).toEqual({
183224
statusCode: 200,
@@ -194,9 +235,8 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
194235
const response = new MockResponse(200, headers, body)
195236
return new Promise(
196237
(resolve, reject) => {
197-
const context = new MockContext(resolve)
198-
awsServerlessExpress.forwardResponseToApiGateway(
199-
server, response, context)
238+
const contextResolver = getContextResolver(resolve)
239+
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
200240
}
201241
).then(successResponse => expect(successResponse).toEqual({
202242
statusCode: 200,
@@ -213,9 +253,8 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
213253
const response = new MockResponse(200, headers, body)
214254
return new Promise(
215255
(resolve, reject) => {
216-
const context = new MockContext(resolve)
217-
awsServerlessExpress.forwardResponseToApiGateway(
218-
server, response, context)
256+
const contextResolver = getContextResolver(resolve)
257+
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
219258
}
220259
).then(successResponse => expect(successResponse).toEqual({
221260
statusCode: 200,
@@ -232,9 +271,8 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
232271
const response = new MockResponse(200, headers, body)
233272
return new Promise(
234273
(resolve, reject) => {
235-
const context = new MockContext(resolve)
236-
awsServerlessExpress.forwardResponseToApiGateway(
237-
server, response, context)
274+
const contextResolver = getContextResolver(resolve)
275+
awsServerlessExpress.forwardResponseToApiGateway(server, response, contextResolver)
238276
}
239277
).then(successResponse => expect(successResponse).toEqual({
240278
statusCode: 200,
@@ -244,3 +282,52 @@ describe('forwardResponseToApiGateway: content-type encoding', () => {
244282
}))
245283
})
246284
})
285+
286+
describe('makeResolver', () => {
287+
test('CONTEXT_SUCCEED (specified)', () => {
288+
return new Promise(
289+
(resolve, reject) => {
290+
const context = new MockContext(resolve)
291+
const contextResolver = awsServerlessExpress.makeResolver({
292+
context,
293+
resolutionMode: 'CONTEXT_SUCCEED'
294+
})
295+
296+
return contextResolver.succeed({
297+
response: 'success'
298+
})
299+
}).then(successResponse => expect(successResponse).toEqual('success'))
300+
})
301+
302+
test('CALLBACK', () => {
303+
const callback = (e, response) => response
304+
const callbackResolver = awsServerlessExpress.makeResolver({
305+
callback,
306+
resolutionMode: 'CALLBACK'
307+
})
308+
const successResponse = callbackResolver.succeed({
309+
response: 'success'
310+
})
311+
312+
expect(successResponse).toEqual('success')
313+
})
314+
315+
test('PROMISE', () => {
316+
return new Promise((resolve, reject) => {
317+
const promise = {
318+
resolve,
319+
reject
320+
}
321+
const promiseResolver = awsServerlessExpress.makeResolver({
322+
promise,
323+
resolutionMode: 'PROMISE'
324+
})
325+
326+
return promiseResolver.succeed({
327+
response: 'success'
328+
})
329+
}).then(successResponse => {
330+
expect(successResponse).toEqual('success')
331+
})
332+
})
333+
})

src/index.js

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function mapApiGatewayEventToHttpRequest (event, context, socketPath) {
5151
}
5252
}
5353

54-
function forwardResponseToApiGateway (server, response, context) {
54+
function forwardResponseToApiGateway (server, response, resolver) {
5555
let buf = []
5656

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

91-
context.succeed(successResponse)
91+
resolver.succeed({ response: successResponse })
9292
})
9393
}
9494

95-
function forwardConnectionErrorResponseToApiGateway (server, error, context) {
95+
function forwardConnectionErrorResponseToApiGateway (error, resolver) {
9696
console.log('ERROR: aws-serverless-express connection error')
9797
console.error(error)
9898
const errorResponse = {
@@ -101,10 +101,10 @@ function forwardConnectionErrorResponseToApiGateway (server, error, context) {
101101
headers: {}
102102
}
103103

104-
context.succeed(errorResponse)
104+
resolver.succeed({ response: errorResponse })
105105
}
106106

107-
function forwardLibraryErrorResponseToApiGateway (server, error, context) {
107+
function forwardLibraryErrorResponseToApiGateway (error, resolver) {
108108
console.log('ERROR: aws-serverless-express error')
109109
console.error(error)
110110
const errorResponse = {
@@ -113,13 +113,13 @@ function forwardLibraryErrorResponseToApiGateway (server, error, context) {
113113
headers: {}
114114
}
115115

116-
context.succeed(errorResponse)
116+
resolver.succeed({ response: errorResponse })
117117
}
118118

119-
function forwardRequestToNodeServer (server, event, context) {
119+
function forwardRequestToNodeServer (server, event, context, resolver) {
120120
try {
121121
const requestOptions = mapApiGatewayEventToHttpRequest(event, context, getSocketPath(server._socketPathSuffix))
122-
const req = http.request(requestOptions, (response, body) => forwardResponseToApiGateway(server, response, context))
122+
const req = http.request(requestOptions, (response) => forwardResponseToApiGateway(server, response, resolver))
123123
if (event.body) {
124124
if (event.isBase64Encoded) {
125125
event.body = Buffer.from(event.body, 'base64')
@@ -128,10 +128,10 @@ function forwardRequestToNodeServer (server, event, context) {
128128
req.write(event.body)
129129
}
130130

131-
req.on('error', (error) => forwardConnectionErrorResponseToApiGateway(server, error, context))
131+
req.on('error', (error) => forwardConnectionErrorResponseToApiGateway(error, resolver))
132132
.end()
133133
} catch (error) {
134-
forwardLibraryErrorResponseToApiGateway(server, error, context)
134+
forwardLibraryErrorResponseToApiGateway(error, resolver)
135135
return server
136136
}
137137
}
@@ -182,13 +182,56 @@ function createServer (requestListener, serverListenCallback, binaryTypes) {
182182
return server
183183
}
184184

185-
function proxy (server, event, context) {
186-
if (server._isListening) {
187-
forwardRequestToNodeServer(server, event, context)
188-
return server
189-
} else {
190-
return startServer(server)
191-
.on('listening', () => proxy(server, event, context))
185+
function proxy (server, event, context, resolutionMode, callback) {
186+
// DEPRECATED: Legacy support
187+
if (!resolutionMode) {
188+
const resolver = makeResolver({ context, resolutionMode: 'CONTEXT_SUCCEED' })
189+
if (server._isListening) {
190+
forwardRequestToNodeServer(server, event, context, resolver)
191+
return server
192+
} else {
193+
return startServer(server)
194+
.on('listening', () => proxy(server, event, context))
195+
}
196+
}
197+
198+
return {
199+
promise: new Promise((resolve, reject) => {
200+
const promise = {
201+
resolve,
202+
reject
203+
}
204+
const resolver = makeResolver({
205+
context,
206+
callback,
207+
promise,
208+
resolutionMode
209+
})
210+
211+
if (server._isListening) {
212+
forwardRequestToNodeServer(server, event, context, resolver)
213+
} else {
214+
startServer(server)
215+
.on('listening', () => forwardRequestToNodeServer(server, event, context, resolver))
216+
}
217+
})
218+
}
219+
}
220+
221+
function makeResolver (params/* {
222+
context,
223+
callback,
224+
promise,
225+
resolutionMode
226+
} */) {
227+
return {
228+
succeed: (params2/* {
229+
response
230+
} */) => {
231+
if (params.resolutionMode === 'CONTEXT_SUCCEED') return params.context.succeed(params2.response)
232+
if (params.resolutionMode === 'CALLBACK') return params.callback(null, params2.response)
233+
if (params.resolutionMode === 'PROMISE') return params.promise.resolve(params2.response)
234+
}
192235
}
193236
}
194237

@@ -205,4 +248,5 @@ if (process.env.NODE_ENV === 'test') {
205248
exports.forwardRequestToNodeServer = forwardRequestToNodeServer
206249
exports.startServer = startServer
207250
exports.getSocketPath = getSocketPath
251+
exports.makeResolver = makeResolver
208252
}

0 commit comments

Comments
 (0)