ERR_STREAM_WRITE_AFTER_END when using gzipResponse with serveStaticFiles
I am getting ERR_STREAM_WRITE_AFTER_END when requesting a static file (through serveStaticFiles) with gzip compression.
e.g:
Error [ERR_STREAM_WRITE_AFTER_END]: write after end
at new NodeError (internal/errors.js:322:7)
at Gzip.Writable.write (internal/streams/writable.js:292:11)
at flush (webpack-internal:///../../node_modules/.pnpm/[email protected]/node_modules/restify/lib/response.js:892:13)
at ServerResponse.__send (webpack-internal:///../../node_modules/.pnpm/[email protected]/node_modules/restify/lib/response.js:546:24)
at ServerResponse.send (webpack-internal:///../../node_modules/.pnpm/[email protected]/node_modules/restify/lib/response.js:321:21)
at Server._finishReqResCycle (webpack-internal:///../../node_modules/.pnpm/[email protected]/node_modules/restify/lib/server.js:1398:13)
at Server._afterRoute (webpack-internal:///../../node_modules/.pnpm/[email protected]/node_modules/restify/lib/server.js:1105:10)
at afterRouter (webpack-internal:///../../node_modules/.pnpm/[email protected]/node_modules/restify/lib/server.js:1075:18)
at nextTick (webpack-internal:///../../node_modules/.pnpm/[email protected]/node_modules/restify/lib/chain.js:116:24)
at processTicksAndRejections (internal/process/task_queues.js:77:11)
- [x] Used appropriate template for the issue type
- [x] Searched both open and closed issues for duplicates of this issue
- [x] Title adequately and concisely reflects the feature or the bug
Restify Version: 8.6.1 Node.js Version: 14.18.1
Expected behaviour
Serve the files, gzipped, without error.
Actual behaviour
ERR_STREAM_WRITE_AFTER_END error is raised, crashing the application.
Repro case
I am getting ERR_STREAM_WRITE_AFTER_END in the following scenario:
- gzipResponse is in place
- serveStaticFiles in in place
- A request is made for a static file with the header:
Accept-Encoding: gzip - The file is binary. (Text files seem to work fine!)
Strangely, the following scenarios work as expected:
- requests for static files without the header
- requests for non-static files with the header (they are gzipped correctly)
Cause
The error seems to be produced by: https://github.com/restify/node-restify/blob/8.x/lib/server.js#L1389
i.e. Restify believes that no response has been written by the end of the handler chain, when in fact it has.
This is because the res.__flushed flag is set to false in this scenario.
I'm not sure when this flag should be getting set and why it is not set only with this specific combination of middleware.
I can also see the problem with another middleware (apollo-server-restify). It seems to interact with res in a simple way. i.e:
res.setHeader('Content-Type', 'application/json');
res.write(gqlResponse);
res.end();
next();
So I wonder if this problem might be present when using gzipResponse with any middleware that bypasses res.send. Then again, I can't see anything special about res.send either.
Workaround
I am able to work around the problem by manually setting this flag. i.e:
const markFlushed = (req, res, next) => {
res._flushed = true;
next();
}
const servePublicFiles = [ restify.plugins.serveStaticFiles('some/path/'), markFlushed ];
httpd.head(publicPaths, servePublicFiles);
httpd.get(publicPaths, servePublicFiles);
Are you willing and able to fix this?
Unknown. (I've not been able to pin down the exact cause so far.)
Does anyone have any ideas what could be causing this?
Apologies if this is being caused by something strange in my set-up.
I get this too since 8.6.1 but only when using res.end() (also with gzipResponse but I think that might be a red herring?)
res.send() sets res._flushed and so we never hit the error.
this is odd because I would expect res.end() to trigger the finish event and hit onResFinish in restify's server.js
EDIT: using node 16.17.0
Getting the same issue too. I had to use res.flushHeaders().
FWIW, this still seems to be an issue. (I'm probably unusual in using Restify in this way, so I guess most people won't encounter this.) I've seen it in other instances when middleware's bypass restify's response API and call res.end directly.
I think it might come from the extra asynchronicity (and delay) introduced by gzip. - From what I can tell functions are being executed in the wrong order. i.e. It's some sort of race condition.