Hot reloading with express and chokidar causes a http headers sent error when using multiple routes

只愿长相守 提交于 2021-02-19 15:54:59

问题


I've been trying a variety of setups for hot reloading and one that I've come across is the https://github.com/glenjamin/ultimate-hot-reloading-example/. Modifying this boilerplate code as a starting point, I've come across the following problem in my server code:

// server.js
import chokidar from 'chokidar';
import express from 'express';

const app = express();

// this is the middleware for handline all of my routes
app.use(function (req, res, next) {
  require('./server/index')(req, res, next);
  // if I commented out any additional routes, the setup would work fine
  require('./server/foo')(req, res, next);
  require('./server/catch-all')(req, res, next);
});

//this watches the server folder for changes
const watcher = chokidar.watch('./server');

watcher.on('ready', function () {
  watcher.on('all', function () {
    console.log("Clearing /server/ module cache from server");
    Object.keys(require.cache).forEach(function (id) {
      if (/[\/\\]server[\/\\]/.test(id)) delete require.cache[id];
    });
  });
});

app.listen(3000, 'localhost', function (err) {
  if (err) throw err;
  const addr = this.address();
  console.log('Listening at http://%s:%d', addr.address, addr.port);
});

The above is the server code that handles clearing the cache by watching for changes with the chokidar module. If I have just one route required inside the app.use middleware function (which listens for every incoming request), I can get it to work. However if have multiple routes, the following error occurs:

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

This is a common issue posted on stack overflow, but all of the solutions I've come across and attempted haven't worked. My route files are as follows:

//index.js
import express from 'express';

const router = express.Router();

router.get('/', (req, res, next) => {
  res.send("greagrehgarhegrehuh").end();
  return next('router');
});

module.exports = router;
//end of index.js

//foo.js
import express from 'express';

const router = express.Router();

router.get('/foo', (req, res, next) => {
  res.send("foo").end();
  return next('router');
});

module.exports = router;
//end of foo.js

//catch-all.js
import express from 'express';

const router = express.Router();

router.get('*', (req, res, next) => {
  res.send("catch all").end();
  return next('router');
});

module.exports = router;
// end of catch-all.js

All three routes do the same thing, bar the endpoint. So far I've explicitly called end on each to end the response, used return next('router') to skip the rest of the middleware functions and have also tried doing it without the above as well. Any ideas on what I'm missing here to get this working? Here's a github project that showcases the issue

https://github.com/RonanQuigley/express-chokidar-hot-reload

UPDATE

So I actually removed the next calls and seem to have almost got it working by doing the following:

app.use(function (req, res, next) {
  require('./server/index')(req, res, next);
  require('./server/foo')(req, res, next);
}); 

// a second app.use middleware, that does the same 
// as the catch all // * router.get from my original post
app.use(function (req, res, next) {
  app.get('*', (req, res) => res.send('catch all'));
})

However, I can't use this second app.use with another require call to a file with an express router like the others. So it seems that express runs through the middleware stack, reaches the * and tries to set the header twice.

The reason I need the * is normally if a user requests an endpoint that doesn't exist, Node correctly shows up with cannot GET/. However, for some reason, with the setup I've outlined express will then crash. My workaround is using * at the end of the middleware stack and I'd just use a res.redirect to send the user back to wherever, but this causes the above issue I've outlined in my original post. So not sure how to get around that one.

So currently I have either:

1) Hot reloading works without the require for a router.get('*'), but when the user navigates to an endpoint that doesn't exist, express will crash.

2) Hot reloading works with the app.get('*') inside a second app.use call, but I can't then use a router to move this into a separate file.


回答1:


Okay, so posting this solution up for my own future reference and in case somebody else stumbles into this problem.

After speaking with the express devs, it turns out that this is indeed possible with a combination of the following:

// you need to use comma separated routes
app.use(
   dynamic('./server/index'), 
   dynamic('./server/foo')
);

// require the library at runtime and apply the req, res, next arguments
function dynamic(lib) {
  return function (req, res, next) {
    return require(lib).apply(this, arguments)
  }
}

In the case of webpack, this would break it as you can't use require as an expression. So use the following to get around that:

function createRoutes(router) {
    const dynamic = (lib) => {
        return function (req, res, next) {
            // let webpack generate a regex expression from this require
            // if we don't you would get a critical dependency warning
            // which would result in the routes not being found
            return require("./src/" + lib + ".js").apply(this, arguments);
        }
    }
    router.use(
        dynamic('index'),
        dynamic('foo'),
    );
    return router;
} 



回答2:


Let's step back a bit and talk about middleware.

Say you have a function which runs some kind of middleware.

const runMiddleware = (req, res, next) => {
  console.log(`this will run everytime a HTTP request comes in`);
}

Then to use that middleware within express:

app.use(runMiddleware);

Every time any (GET, POST, DELETE, etc) request comes in, this function is run.

Essentially you are doing the same thing below - You are wrapping three (3) route calls with a single function. This function is calling all of these routes at once, hence res is actually being sent 3 times in a row in the example below:

app.use(function (req, res, next) { // runs every time any request comes in
  require('./server/index')(req, res, next); // res sent, ok
  require('./server/foo')(req, res, next); // res sent, err
  require('./server/catch-all')(req, res, next); // res sent, err
}); 

Here is a basic way of handling routes:

const index = require('./server/index');
const foo = require('./server/foo');

app.use('/', index);
app.use('/foo', foo);

// catch everything else
app.use(function (req, res) {
  res.send('catch all');
})


来源:https://stackoverflow.com/questions/49129577/hot-reloading-with-express-and-chokidar-causes-a-http-headers-sent-error-when-us

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!