In my app I need to synchronize some resources nightly. The synchronization is started by sending PUT request to /api/sync/
with a JSON array of objects in the body. My task is to mirror the data in JSON in my app, so firstly I must resolve what needs to be added, updated and deleted, then issue appropriate queries to the database. That could take a while so I don't like any other request to get in a way.
So I need to close Express' connections, do the synchronization and start listening again. But there are 2 problems:
http.Server.close()
prevents receiving new connections but waits for already executing to finishPUT /api/sync/
there could be another request received just before previous one can invoke close()
. Thus, 2 incoming PUT /api/sync/
request would be accepted into processing.At the moment I create the Express app this way:
// index.js
const app = express();
app.server = new Server(app);
app.server.start();
// Server.ts
export class Server {
public readonly server: http.Server;
private readonly close: () => Promise<http.Server>;
private readonly listen: (port: number) => Promise<http.Server>;
constructor(public readonly app: express.Application) {
this.server = http.createServer(app);
this.close = promisify<http.Server>(this.server.close.bind(this.server));
this.listen = promisify<http.Server>(this.server.listen.bind(this.server));
}
async start() {
console.log('START');
await this.listen(Number(process.env.PORT));
}
async stop() {
console.log('STOP');
if (!this.server.listening) {
throw new Error('Server is not listening');
}
await this.close();
}
}
// middleware
module.exports = fn => (req, res, next) => {
console.log('START MAINTENANCE');
req.app.server.stop()
.then(() => {
const endMaintenance = (err) => {
console.log('END MAINTENANCE');
req.app.server.start().then(() => next(err));
};
Promise.resolve(fn(req, res)).then(() => endMaintenance())
.catch(err => endMaintenance(err));
});
};
// route definition
routes.put(
'/',
exclusiveRun(
async (req: Request, res: Response) => {
await dataSync.synchronize(req.body);
res.sendStatus(204);
}
)
);
But still, when I send 2 requests to /api/sync
endpoint in a row, Express accepts them and only then one of them throws "Server is not listening" error. Where do I make an error? Is that approach enough to prevent disrupting the sync process (or otherwise causing trouble when sync is underway)?
Rather than shutting down the server, you could have as your first middleware a request handler that detects if your server is doing maintenance (some flag you have) and, if so, it just immediately fails with a 5xx status code all incoming requests.
Then, when your /api/sync
request comes in, you set that flag (blocking any more requests) and you start the sync when all currently processing requests are done. Or, you could combine this logic with actually shutting down the server so any other requests that come in before the server actually shuts down, get a 5xx response.