Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERR_STREAM_WRITE_AFTER_END when using gzipResponse with serveStaticFiles #1915

Open
3 tasks done
daniel-ac-martin opened this issue Jun 6, 2022 · 3 comments
Open
3 tasks done

Comments

@daniel-ac-martin
Copy link

daniel-ac-martin commented Jun 6, 2022

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/restify@8.6.1/node_modules/restify/lib/response.js:892:13)                                                                          
    at ServerResponse.__send (webpack-internal:///../../node_modules/.pnpm/restify@8.6.1/node_modules/restify/lib/response.js:546:24)                                                          
    at ServerResponse.send (webpack-internal:///../../node_modules/.pnpm/restify@8.6.1/node_modules/restify/lib/response.js:321:21)                                                            
    at Server._finishReqResCycle (webpack-internal:///../../node_modules/.pnpm/restify@8.6.1/node_modules/restify/lib/server.js:1398:13)                                                       
    at Server._afterRoute (webpack-internal:///../../node_modules/.pnpm/restify@8.6.1/node_modules/restify/lib/server.js:1105:10)                                                              
    at afterRouter (webpack-internal:///../../node_modules/.pnpm/restify@8.6.1/node_modules/restify/lib/server.js:1075:18)                                                                     
    at nextTick (webpack-internal:///../../node_modules/.pnpm/restify@8.6.1/node_modules/restify/lib/chain.js:116:24)                                                                          
    at processTicksAndRejections (internal/process/task_queues.js:77:11)        
  • Used appropriate template for the issue type
  • Searched both open and closed issues for duplicates of this issue
  • 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.

@hanaan-yousaf
Copy link

hanaan-yousaf commented Oct 26, 2022

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

@juneidy
Copy link

juneidy commented Mar 13, 2023

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

@daniel-ac-martin
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants