FoalTS / foal

Full-featured Node.js framework 🚀
https://foalts.org/
MIT License
1.9k stars 142 forks source link

Returning a large response causes "premature close" error #1159

Closed haneefkassam closed 2 years ago

haneefkassam commented 2 years ago

I don't know if I'm doing something wrong, but I've encountered a confusing error and seem to be struggling to make any headway.

I have an endpoint whose purpose is to allow users using my API to download large files (2GB+). A scaled down version of my endpoint looks as such:

@Get('/download')
async download(ctx: Context) {
  const filename = path.join(__dirname, "some-hash.zip");
  const readStream: fs.ReadStream = fs.createReadStream(filename);

  let response = new HttpResponseOK(readStream, { stream: true });
  response.setHeader('Content-Type', 'application/zip');
  response.setHeader('Cache-Control', 'no-cache');
  response.setHeader('Content-Length', '<byte length pulled from archiving utility>'); //this is correct as the export works for smaller files.  For this big file, the value of this is 1919523322.
  response.setHeader('Content-Disposition', 'attachment; filename=export.zip');
  return response;
}

For small files (10s to 100s of mb), the download works fine. When I'm trying to export a large file (the one I'm working with is 1.9GB), I get a "premature close" error:

[1] Error: Premature close
[1]     at new NodeError (node:internal/errors:371:5)
[1]     at ServerResponse.onclose (node:internal/streams/end-of-stream:140:30)
[1]     at ServerResponse.emit (node:events:532:35)
[1]     at Socket.onServerResponseClose (node:_http_server:235:23)
[1]     at Socket.emit (node:events:532:35)
[1]     at TCP.<anonymous> (node:net:687:12) {
[1]   code: 'ERR_STREAM_PREMATURE_CLOSE'
[1] }

This error is thrown before my browser asks me to download the file, and if I "save" the download in the browser, the download hangs (of course, because the socket is closed). However, if I extract the zip locally (on the server), all of the files I expect are there. I run into this very same error if I try saving the file using the disk service and sending the response via

@Get('/export')
async export(ctx: Context) {
  // save file to disk service, or use existing file in disk service
  return this.disk.createHttpResponse('<file>', { forceDownload: true, filename: 'export.zip'});

I'm lost on the reason for this error. Is this Node prematurely breaking the connection? Is something in Foal or Express causing the socket to break? Should I be returning the response differently than how I have it above? Any help in resolving this would be greatly appreciated.

haneefkassam commented 2 years ago

Nvm... closing this issue. The problem was the proxy in front of the foal service not keeping the connection alive.

LoicPoullain commented 2 years ago

Glad you found how to solve this and thank you for having shared the solution 👍