node-restify icon indicating copy to clipboard operation
node-restify copied to clipboard

ERR_STREAM_WRITE_AFTER_END when using gzipResponse with serveStaticFiles

Open daniel-ac-martin opened this issue 3 years ago • 3 comments

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:

  1. gzipResponse is in place
  2. serveStaticFiles in in place
  3. A request is made for a static file with the header: Accept-Encoding: gzip
  4. 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.

daniel-ac-martin avatar Jun 06 '22 20:06 daniel-ac-martin

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

hanaan-yousaf avatar Oct 26 '22 16:10 hanaan-yousaf

Getting the same issue too. I had to use res.flushHeaders().

juneidy avatar Mar 13 '23 03:03 juneidy

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.

daniel-ac-martin avatar Feb 24 '24 18:02 daniel-ac-martin