diff --git a/.changeset/honest-lamps-smell.md b/.changeset/honest-lamps-smell.md new file mode 100644 index 000000000..54278a33a --- /dev/null +++ b/.changeset/honest-lamps-smell.md @@ -0,0 +1,11 @@ +--- +"@opennextjs/aws": minor +--- + +perf(OpenNextResponse): do not store the chunks for streamed responses + +There is no need to store the chunks for streamed responses. +Not storing the chunks allows saving memory. + +BREAKING CHANGE: Note that `OpenNextHandler` will now return an empty body if your wrapper provides a `StreamCreator` +This could break custom converters. diff --git a/packages/open-next/src/http/openNextResponse.ts b/packages/open-next/src/http/openNextResponse.ts index 59f6b8eb8..45ff8425f 100644 --- a/packages/open-next/src/http/openNextResponse.ts +++ b/packages/open-next/src/http/openNextResponse.ts @@ -20,11 +20,13 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse { statusCode!: number; statusMessage = ""; headers: OutgoingHttpHeaders = {}; - private _cookies: string[] = []; - private responseStream?: Writable; headersSent = false; _chunks: Buffer[] = []; + private _cookies: string[] = []; + private responseStream?: Writable; + private bodyLength = 0; + // To comply with the ServerResponse interface : strictContentLength = false; assignSocket(_socket: Socket): void { @@ -282,16 +284,13 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse { return Buffer.concat(this._chunks); } - getBodyLength(): number { - let size = 0; - for (const chunk of this._chunks) { - size += chunk.length; - } - return size; - } - private _internalWrite(chunk: any, encoding: BufferEncoding) { - this._chunks.push(Buffer.from(chunk, encoding)); + const buffer = Buffer.from(chunk, encoding); + this.bodyLength += buffer.length; + if (!this.streamCreator) { + // Do not keep chunks around for streamed responses + this._chunks.push(buffer); + } this.push(chunk, encoding); this.streamCreator?.onWrite?.(); } @@ -314,12 +313,11 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse { this.flushHeaders(); } // In some cases we might not have a store i.e. for example in the image optimization function - // We may want to reconsider this in the future, it might be intersting to have access to this store everywhere + // We may want to reconsider this in the future, it might be interesting to have access to this store everywhere globalThis.__openNextAls ?.getStore() ?.pendingPromiseRunner.add(this.onEnd(this.headers)); - const bodyLength = this.getBodyLength(); - this.streamCreator?.onFinish?.(bodyLength); + this.streamCreator?.onFinish?.(this.bodyLength); //This is only here because of aws broken streaming implementation. //Hopefully one day they will be able to give us a working streaming implementation in lambda for everyone @@ -328,7 +326,7 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse { //BE CAREFUL: Aws keeps rolling out broken streaming implementations even on accounts that had working ones before //This is not dependent on the node runtime used if ( - bodyLength === 0 && + this.bodyLength === 0 && // We use an env variable here because not all aws account have the same behavior // On some aws accounts the response will hang if the body is empty // We are modifying the response body here, this is not a good practice