Skip to content

Commit f1401eb

Browse files
committed
fix: #1189 write prelude in streamifyResponse with string body
1 parent 5a6e7ed commit f1401eb

File tree

2 files changed

+104
-43
lines changed

2 files changed

+104
-43
lines changed

packages/core/__tests__/index.js

Lines changed: 103 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -661,15 +661,63 @@ test('"onError" middleware should be able to short circuit response', async (t)
661661
})
662662

663663
// streamifyResponse
664+
665+
// mock implementation awslambda.HttpResponseStream
666+
const DELIMITER_LEN = 8
664667
globalThis.awslambda = {
665668
streamifyResponse: (cb) => cb,
666669
HttpResponseStream: {
667-
from: (responseStream, metadata) => {
668-
return responseStream
670+
from: (underlyingStream, prelude) => {
671+
// https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/main/src/HttpResponseStream.js
672+
// Wrap the underlyingStream to ensure _onBeforeFirstWrite is called before the first write operation
673+
const wrapStream = () => {
674+
let isFirstWrite = true
675+
const originalWrite = underlyingStream.write
676+
underlyingStream.write = (...args) => {
677+
if (
678+
isFirstWrite &&
679+
typeof underlyingStream._onBeforeFirstWrite === 'function'
680+
) {
681+
isFirstWrite = false
682+
underlyingStream._onBeforeFirstWrite()
683+
}
684+
return originalWrite.apply(underlyingStream, args)
685+
}
686+
687+
return underlyingStream
688+
}
689+
690+
// Execute _onBeforeFirstWrite before the first write operation
691+
underlyingStream._onBeforeFirstWrite = () => {
692+
const metadataPrelude = JSON.stringify(prelude)
693+
underlyingStream.write(metadataPrelude)
694+
underlyingStream.write(new Uint8Array(DELIMITER_LEN))
695+
}
696+
return wrapStream()
669697
}
670698
}
671699
}
672700

701+
function createResponseStreamMockAndCapture () {
702+
function processChunkResponse (chunkResponse) {
703+
const indexOf = chunkResponse.indexOf(new Uint8Array(DELIMITER_LEN))
704+
const prelude = chunkResponse.slice(0, indexOf)
705+
const content = chunkResponse.slice(indexOf + DELIMITER_LEN * 2 - 1)
706+
return { prelude, content }
707+
}
708+
709+
let chunkResponse = ''
710+
const responseStream = createWritableStream((chunk) => {
711+
chunkResponse += chunk
712+
})
713+
return {
714+
responseStream,
715+
chunkResponse: () => chunkResponse,
716+
prelude: () => processChunkResponse(chunkResponse).prelude,
717+
content: () => processChunkResponse(chunkResponse).content
718+
}
719+
}
720+
673721
test('Should throw with streamifyResponse:true using object', async (t) => {
674722
const input = {}
675723
const handler = middy(
@@ -692,27 +740,28 @@ test('Should throw with streamifyResponse:true using object', async (t) => {
692740

693741
test('Should return with streamifyResponse:true using body undefined', async (t) => {
694742
const input = ''
743+
const metadata = {
744+
statusCode: 200,
745+
headers: {
746+
'Content-Type': 'plain/text'
747+
}
748+
}
695749
const handler = middy(
696750
(event, context, { signal }) => {
697-
return {
698-
statusCode: 200,
699-
headers: {
700-
'Content-Type': 'plain/text'
701-
}
702-
}
751+
return metadata
703752
},
704753
{
705754
streamifyResponse: true
706755
}
707756
)
708757

709-
let chunkResponse = ''
710-
const responseStream = createWritableStream((chunk) => {
711-
chunkResponse += chunk
712-
})
758+
const { responseStream, prelude, content } =
759+
createResponseStreamMockAndCapture()
760+
713761
const response = await handler(event, responseStream, context)
714762
t.is(response, undefined)
715-
t.is(chunkResponse, input)
763+
t.is(prelude(), JSON.stringify(metadata))
764+
t.is(content(), input)
716765
})
717766

718767
test('Should return with streamifyResponse:true using string', async (t) => {
@@ -723,13 +772,11 @@ test('Should return with streamifyResponse:true using string', async (t) => {
723772
return input
724773
})
725774

726-
let chunkResponse = ''
727-
const responseStream = createWritableStream((chunk) => {
728-
chunkResponse += chunk
729-
})
775+
const { responseStream, chunkResponse } = createResponseStreamMockAndCapture()
776+
730777
const response = await handler(event, responseStream, context)
731778
t.is(response, undefined)
732-
t.is(chunkResponse, input)
779+
t.is(chunkResponse(), input)
733780
})
734781

735782
test('Should return with streamifyResponse:true using body string', async (t) => {
@@ -746,13 +793,39 @@ test('Should return with streamifyResponse:true using body string', async (t) =>
746793
}
747794
})
748795

749-
let chunkResponse = ''
750-
const responseStream = createWritableStream((chunk) => {
751-
chunkResponse += chunk
796+
const { responseStream, content } = createResponseStreamMockAndCapture()
797+
const response = await handler(event, responseStream, context)
798+
t.is(response, undefined)
799+
t.is(content(), input)
800+
})
801+
802+
test('Should return with streamifyResponse:true using empty body string and prelude', async (t) => {
803+
const input = ''
804+
const metadata = {
805+
statusCode: 301,
806+
headers: {
807+
'Content-Type': 'plain/text',
808+
Location: 'https://example.com'
809+
}
810+
}
811+
812+
const handler = middy({
813+
streamifyResponse: true
814+
}).handler((event, context, { signal }) => {
815+
return {
816+
...metadata,
817+
body: input
818+
}
752819
})
820+
821+
const { responseStream, prelude, content } =
822+
createResponseStreamMockAndCapture()
823+
753824
const response = await handler(event, responseStream, context)
825+
754826
t.is(response, undefined)
755-
t.is(chunkResponse, input)
827+
t.is(prelude(), JSON.stringify(metadata))
828+
t.is(content(), input)
756829
})
757830

758831
test('Should return with streamifyResponse:true using ReadableStream', async (t) => {
@@ -766,13 +839,10 @@ test('Should return with streamifyResponse:true using ReadableStream', async (t)
766839
}
767840
)
768841

769-
let chunkResponse = ''
770-
const responseStream = createWritableStream((chunk) => {
771-
chunkResponse += chunk
772-
})
842+
const { responseStream, chunkResponse } = createResponseStreamMockAndCapture()
773843
const response = await handler(event, responseStream, context)
774844
t.is(response, undefined)
775-
t.is(chunkResponse, input)
845+
t.is(chunkResponse(), input)
776846
})
777847

778848
test('Should return with streamifyResponse:true using body ReadableStream', async (t) => {
@@ -792,13 +862,10 @@ test('Should return with streamifyResponse:true using body ReadableStream', asyn
792862
}
793863
)
794864

795-
let chunkResponse = ''
796-
const responseStream = createWritableStream((chunk) => {
797-
chunkResponse += chunk
798-
})
865+
const { responseStream, content } = createResponseStreamMockAndCapture()
799866
const response = await handler(event, responseStream, context)
800867
t.is(response, undefined)
801-
t.is(chunkResponse, input)
868+
t.is(content(), input)
802869
})
803870

804871
test('Should return with streamifyResponse:true using ReadableStream.pipe(...)', async (t) => {
@@ -812,13 +879,10 @@ test('Should return with streamifyResponse:true using ReadableStream.pipe(...)',
812879
}
813880
)
814881

815-
let chunkResponse = ''
816-
const responseStream = createWritableStream((chunk) => {
817-
chunkResponse += chunk
818-
})
882+
const { responseStream, chunkResponse } = createResponseStreamMockAndCapture()
819883
const response = await handler(event, responseStream, context)
820884
t.is(response, undefined)
821-
t.is(chunkResponse, input)
885+
t.is(chunkResponse(), input)
822886
})
823887

824888
test('Should return with streamifyResponse:true using body ReadableStream.pipe(...)', async (t) => {
@@ -838,13 +902,10 @@ test('Should return with streamifyResponse:true using body ReadableStream.pipe(.
838902
}
839903
)
840904

841-
let chunkResponse = ''
842-
const responseStream = createWritableStream((chunk) => {
843-
chunkResponse += chunk
844-
})
905+
const { responseStream, content } = createResponseStreamMockAndCapture()
845906
const response = await handler(event, responseStream, context)
846907
t.is(response, undefined)
847-
t.is(chunkResponse, input)
908+
t.is(content(), input)
848909
})
849910

850911
// Plugin

packages/core/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const middy = (lambdaHandler = defaultLambdaHandler, plugin = {}) => {
7272
const size = 16384 // 16 * 1024 // Node.js default
7373
let position = 0
7474
const length = input.length
75-
while (position < length) {
75+
while (position <= length) {
7676
yield input.substring(position, position + size)
7777
position += size
7878
}

0 commit comments

Comments
 (0)