diff --git a/.changeset/techdocs-glasses-wonder.md b/.changeset/techdocs-glasses-wonder.md new file mode 100644 index 0000000000..7610be2665 --- /dev/null +++ b/.changeset/techdocs-glasses-wonder.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +TechDocs backend now streams files through from Google Cloud Storage to the browser, improving memory usage. diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.ts b/packages/techdocs-common/src/stages/publish/googleStorage.ts index e6b2c26943..9c28de4130 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.ts @@ -170,29 +170,24 @@ export class GoogleGCSPublish implements PublisherBase { const fileExtension = path.extname(filePath); const responseHeaders = getHeadersForFileExtension(fileExtension); - const fileStreamChunks: Array = []; + // Pipe file chunks directly from storage to client. this.storageClient .bucket(this.bucketName) .file(filePath) .createReadStream() + .on('pipe', () => { + res.writeHead(200, responseHeaders); + }) .on('error', err => { this.logger.warn(err.message); - res.status(404).send(err.message); - }) - .on('data', chunk => { - fileStreamChunks.push(chunk); - }) - .on('end', () => { - const fileContent = Buffer.concat(fileStreamChunks); - // Inject response headers - for (const [headerKey, headerValue] of Object.entries( - responseHeaders, - )) { - res.setHeader(headerKey, headerValue); + // Send a 404 with a meaningful message if possible. + if (!res.headersSent) { + res.status(404).send(err.message); + } else { + res.destroy(); } - - res.send(fileContent); - }); + }) + .pipe(res); }; }