ECONNRESET in Express.js (Node.js) with multiple requests - javascript

Given a standard Express.js setup
const express = require('express');
const app = express();
const router = express.Router();
router.get('/test/:id', (req, res) => {
return res.status(200).json({ hello: 'world' });
});
app.use('/api', router);
app.listen(3000, () => console.info('Up on port 3000));
I am making 1000 requests agains the endpoint, one after the other:
const fetch = require('node-fetch');
for (let i = 0; i < 1000; i++) {
let id = Math.floor(Math.random() * 12) + 1;
fetch(`http://localhost:3000/api/test/${id}`).then(res => res.json()).then(data => console.log(data)).catch(error => console.error(error));
}
I do see the data returned however, every now and then I see an ECONNRESET error. The amount of ECONNRESET error messages also vary: sometimes I get a few, sometimes a lot more. I do understand the message but I can't get my head around solving the issue behind it.
Here's a sample error:
{ FetchError: request to http://localhost:3000/api/test/8 failed, reason: connect ECONNRESET 127.0.0.1:3000
at ClientRequest.<anonymous> (node_modules/node-fetch/lib/index.js:1345:11)
at ClientRequest.emit (events.js:182:13)
at Socket.socketErrorListener (_http_client.js:399:9)
at Socket.emit (events.js:182:13)
at emitErrorNT (internal/streams/destroy.js:82:8)
at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
at process.internalTickCallback (internal/process/next_tick.js:72:19)
message:
'request to http://localhost:3000/api/departments/8 failed, reason: connect ECONNRESET 127.0.0.1:3000',
type: 'system',
errno: 'ECONNRESET',
code: 'ECONNRESET' }
Note that I have tried to make the request using axios, the built-in HTTP module all to avail. I'm sure the issue is with my Express app handling the request but not sure how to fix it exactly.
Update 1:
As per the suggestion in the comment, here's the async version:
async function f() {
const array = Array.from(Array(1000).keys());
for (const el of array) {
try {
let id = Math.floor(Math.random() * 12) + 1;
const result = await fetch(`http://localhost:3000/api/test/${id}`).then(res => res.json());
console.log(result);
return result;
} catch(e) {
console.log(e);
}
}
}
f();
Now I am receiving occasional ECONNREFUSED messages.
Update 2:
Based on Mazki516's answer here's the solution that works:
// previous require statements
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const cpuCount = os.cpus().length
for (let i = 0; i < cpuCount; i++) {
cluster.fork()
}
} else {
const app = express();
// rest of the route definitions
// also app.listen() etc...
}
cluster.on('exit', worker => {
console.log(`${worker.id} removed`);
cluster.fork();
});

One of the reasons you see this is because you make the calls in "parallel" .
You do start the calls one after the other , but the loops will end probably before the first results returned from the server.
The loop continues until the end , making the call stack filled with 1000 async requests to the server .
Your'e are hitting hardware/software limits and nothing is wrong with the code.
if you did want to build a server which can handle 1k (and much more) requests concurrently I would take a look into the "cluster" module of node .
Please notice that when doing network job between server , it's acceptable to use a concurrency limit . (for example: up to 4 requests concurrently)
but you can always scale your server beyond one machine and handle much more traffic .

Related

How to bypass clustering

I have an express server and i trying to schedule a job to send automatic emails at a particular time(10:00 am in my case). And i am using node-cron package to schedule jobs.
But i am making use of clustering technique to increase my app performance due to which there are multiple workers created and running constantly. In my implementation i am creating workers equal to total cpu of my machine because of which i have 8 workers running at all times.
To perform a job i have to schedule the job in my server.js, due to which my job i running 8 times at that particular time but i want it to run only once at that particular time.
Below is my server.js:
require("dotenv").config();
const express = require("express");
const { errorHandler } = require("./middleware/errorMiddleware");
const connectDB = require("./config/db");
const cors = require("cors");
const cluster = require("cluster");
const totalCPUs = require("os").cpus().length;
const process = require("process");
var cron = require('node-cron');
const port = process.env.PORT || 5000;
if (cluster.isMaster) {
// console.log(`Number of CPUs is ${totalCPUs}`);
// console.log(`Master ${process.pid} is running`);
// Fork workers.
for (let i = 0; i < totalCPUs; i++) {
cluster.fork();
}
// if any worker dies fork a new worker
cluster.on("exit", (worker, code, signal) => {
// console.log(`worker ${worker.process.pid} died`);
// console.log("Let's fork another worker!");
cluster.fork();
});
} else {
connectDB();
const app = express();
const corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 204
};
app.use(cors(corsOptions))
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use("/api/admin", require("./routes/adminRoutes"));
app.use(errorHandler);
// This job run at 10:00 am every day
cron.schedule('00 10 * * *', function() {
console.log('send email');
});
app.listen(port, () => {
console.log(`Running on ${port}`);
});
}
For this implementation i get send email 8 times in my console.
And if i take out that schedule out of if else block and keep that schedule at the very end of server.js like shown below then i get send email 9 times in my console
// same code as above
app.use(errorHandler);
app.listen(port, () => {
console.log(`Running on ${port}`);
});
}
// This job run at 10:00 am every day
cron.schedule('00 10 * * *', function() {
console.log('send email');
});
I tried few things but could not get the desired output. If there is any other way i can make use of then do tell me. Thanks in advance.
First off, cluster.isMaster has been deprecated since v16 so you should be using cluster.isPrimary instead.
Then, you are putting the cron.schedule() code in the wrong place. You are putting it in the clustered processes when is should be inside the isPrimary block so that it is only run once.
if (cluster.isPrimary) {
cron.shedule(...);
}

res.on() reading error inside https.get() function javascript api

I am attempting to grab data from an API from openWeatherAPI with a correct api key and query (I checked with Postman to ensure the call is correct), but ran into a syntax error. When I try to call the on() function inside of my https.get callback function, I am met with the following error in my terminal:
response.on("data", (data) => {
^
TypeError: Cannot read properties of undefined (reading 'on')
at ClientRequest.<anonymous> (C:file-path\api-prac\app.js:16:18)
at Object.onceWrapper (node:events:628:26)
at ClientRequest.emit (node:events:513:28)
at HTTPParser.parserOnIncomingClient [as onIncoming] (node:_http_client:693:27)
at HTTPParser.parserOnHeadersComplete (node:_http_common:128:17)
at TLSSocket.socketOnData (node:_http_client:534:22)
at TLSSocket.emit (node:events:513:28)
at addChunk (node:internal/streams/readable:315:12)
at readableAddChunk (node:internal/streams/readable:289:9)
at TLSSocket.Readable.push (node:internal/streams/readable:228:10)
My code:
const express = require("express");
const https = require("https");
const app = express()
// what should happen when user tries to go to home page
app.get("/", function(req, res) {
const url = "https://api.openweathermap.org/data/2.5/weather?q=London&appid=my-api-key";
https.get(url, function(req, response) {
console.log("blah blah repsonse");
response.on("data", (data) => {
console.log(data);
// const weatherDatta = JSON.parse(data)
/* extra code will be put here to send a response */
})
});
res.send("server is up");
}
app.listen(3000, function() {
console.log("app running on server 3000");
})
I tried looking at the documentation shown on the https://nodejs.org/api/https.html website, but was unable to find anything that helped outside of what I was already doing with my code.
The arguments for your https.get() callback are wrong. It should be this:
https.get(url, function(response) {
response.on('data', ...);
});
There is no second argument so when you try to make one, it's undefined and does not work.
Code example in the doc here.
Note also that there is no guarantee that you get the entire response in the first data event. The response may arrive in chunks so if you're trying to get the whole response, you should be accumulating all the data events and then processing them all in the end event. And, you should be handling errors in multiple places:
https.get(url, function(response) {
let result = "";
response.on('data', data => {
result += data.toString();
}).on('end', () => {
try {
let weatherData = JSON.parse(result);
// use the weatherData here
} catch(e) {
console.log(e);
// handle JSON parsing error here
}
}).on('error', err => {
console.log(err);
// handle http request error here
});
});
Note, using an http request library such as got() or node-fetch() or even fetch() which is built-in to the newest versions of nodejs will make this code much simpler because they will retrieve the entire response for you and are promise based which makes a number of things including error handling much simpler.
Note how much simpler this is with the got() library.
got(url).json().then(weatherData => {
// use weatherData here
}).catch(err => {
console.log(err);
// handler error here
});

How to mock a Kubernetes API endpoint that returns a socket?

I am mocking some endpoints of the Kubernetes API in a mock server for some integration tests and got stuck in the implementation of the endpoint /apis/batch/v1/watch/namespaces/{namespace}/jobs?watch=true (doc, need to search for batch/v1/watch in the page). The client uses this method to make a GET request and keep the connection open to receive events related to Job resources. Apparently, it handles a 'socket' event.
I implemented a simple mock endpoint that returns the data I want, but I get this error when the request is made:
Error: socket hang up
at connResetException (node:internal/errors:691:14)
at Socket.socketOnEnd (node:_http_client:466:23)
at Socket.emit (node:events:538:35)
at endReadableNT (node:internal/streams/readable:1345:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)
How should this mock be implemented? Do I need to return a socket? How should I do that?
Answering to my own question: after some attempts, all I needed to do was to use res.write() with a string that ends with a line break character, like this:
import express from 'express';
const TIME_BETWEEN_EVENTS = 500; // Milliseconds
const app = express();
app.get('/apis/batch/v1/watch/namespaces/:namespace/jobs', (_, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
console.log('Client connected to Jobs watch endpoint');
let jobEventsSent = 0;
const interval = setInterval(() => {
if (jobEventsSent < 3) {
console.log('Sending data to Jobs watcher');
res.write(
Buffer.from(JSON.stringify({ type: 'ADDED', object: { /* Job Resource */ } }) + '\n'),
);
jobEventsSent++;
} else {
clearInterval(interval)
}
}, TIME_BETWEEN_EVENTS);
res.socket.on('close', () => {
console.log('Client disconnected from Jobs watch endpoint');
if (interval) clearInterval(interval);
});
});
The code above keeps the connection alive event after all the messages have been sent. To close the connection, it is necessary to call res.end();

Node.js cluster error

Hello i am very new to node.js and javascript, i am trying to create a culster.js with the nodejs cluster module, at the end of my if statement i am calling server.js to start the app.
cluster.js
const cluster = require('cluster');
const cpuCount = require('os').cpus().length;
const startServer = require('./server');
if (cluster.isMaster) {
for (let i = 0; i < cpuCount; i += 1) {
cluster.fork();
}
cluster.on('exit', () => {
cluster.fork();
});
} else {
return startServer;
}
server.js
const fs = require('fs');
const path = require('path');
const express = require('express');
const auth = require('http-auth');
const {
createBundleRenderer,
} = require('vue-server-renderer');
const bundle = fs.readFileSync('dist/server.js', 'utf-8');
const renderer = createBundleRenderer(bundle);
function parseIndexHtml() {
const [
entire,
htmlOpen,
htmlOpenTailAndHead,
headCloseAndBodyOpen,
bodyOpenTailAndContentBeforeApp,
contentAfterAppAndHtmlClose,
] = fs.readFileSync('index.html', 'utf8').match(/^([\s\S]+?<html)([\s\S]+?)(<\/head>[\s\S]*?<body)([\s\S]+?)<div id="?app"?><\/div>([\s\S]+)$/);
return {
entire,
htmlOpen,
htmlOpenTailAndHead,
headCloseAndBodyOpen,
bodyOpenTailAndContentBeforeApp,
contentAfterAppAndHtmlClose,
};
}
const indexHtml = parseIndexHtml();
const app = express();
const basicAuth = auth.basic({
realm: 'Jobportal',
}, (username, password, callback) => {
callback(username === 'x' && password === 'x');
});
app.get('/ping', (request, response) => {
response.status(200).end();
});
app.use(auth.connect(basicAuth));
// serve pure static assets
app.use('/public', express.static(path.resolve('./public')));
app.use('/dist', express.static(path.resolve('./dist')));
app.get('*', (request, response) => {
const context = {
url: request.url,
};
renderer.renderToString(context, (error, html) => {
if (error) {
if (error.code === '404') {
response.status(404).end(indexHtml.entire);
} else {
response.status(500).end(indexHtml.entire);
console.error(`Error during render: ${request.url}`); // eslint-disable-line
console.error(error); // eslint-disable-line
}
return;
}
const {
title,
htmlAttrs,
bodyAttrs,
link,
style,
script,
noscript,
meta,
} = context.meta.inject();
response.write(
`${indexHtml.htmlOpen} data-vue-meta-server-rendered ${htmlAttrs.text()} ${indexHtml.htmlOpenTailAndHead}
${meta.text()}
${title.text()}
${link.text()}
${style.text()}
${script.text()}
<script>
window.__INITIAL_STATE__ = ${JSON.stringify(context.initialState)}
</script>
${noscript.text()}
${indexHtml.headCloseAndBodyOpen} ${bodyAttrs.text()} ${indexHtml.bodyOpenTailAndContentBeforeApp}
${html}
<script src="/dist/client.js"></script>
${indexHtml.contentAfterAppAndHtmlClose}`
);
response.end();
});
});
const port = 8181;
// start server
app.listen(port, () => {
console.log(`server started at port ${port}`); // eslint-disable-line
});
I get an error
server started at port 8181
events.js:163
throw er; // Unhandled 'error' event
^
Error: bind EADDRINUSE null:8181
at Object.exports._errnoException (util.js:1050:11)
at exports._exceptionWithHostPort (util.js:1073:20)
at listenOnMasterHandle (net.js:1336:16)
at rr (internal/cluster/child.js:111:12)
at Worker.send (internal/cluster/child.js:78:7)
at process.onInternalMessage (internal/cluster/utils.js:42:8)
at emitTwo (events.js:111:20)
at process.emit (events.js:194:7)
at process.nextTick (internal/child_process.js:766:12)
at _combinedTickCallback (internal/process/next_tick.js:73:7)
events.js:163
throw er; // Unhandled 'error' event
^
Any ideas why ?
EADDRINUSE means that the port number which listen() tries to bind the server to is already in use.
You need to verify if the port is already taken on your system. To do that:
On linux: sudo netstat -nltp | grep (port) in your case is port 8181.
On OSX: sudo lsof -i -P | grep (port)
If you have a result, you need to kill the process (kill <pid>).
You should check if pm2 list returns 0 process. In addition, when you do a pm2 stopAll, the socket is not released. Don't forget to do a pm2 kill to be sure the daemon is killed.
$ pm2 kill
Daemon killed
Verifying for Windows:
C:\> netstat -a -b
a Displays all connections and listening ports.
b Displays the executable involved in creating each connection or listening port. In some cases well-known executables host multiple independent components, and in these cases the sequence of components involved in creating the connection or listening port is displayed. In this case the executable name is in [] at the bottom, on top is the component it called, and so forth until TCP/IP was reached. Note that this option can be time-consuming and will fail unless you have sufficient permissions.
n Displays addresses and port numbers in numerical form.
o Displays the owning process ID associated with each connection.
EXAMPLES to kill in windows command line:
If you know the name of a process to kill, for example notepad.exe, use the following command from a command prompt to end it:
taskkill /IM notepad.exe
To kill a single instance of a process, specify its process id (PID). For example, if the desired process has a PID of 827, use the following command to kill it:
taskkill /PID 827

How to properly close Node.js Express server?

I need to close server after getting callback from /auth/github/callback
url. With usual HTTP API closing
server is currently supporting with server.close([callback])
API function, but with node-express server i’m getting TypeError: Object function app(req, res){ app.handle(req, res); } has no method 'close'
error. And I don't know how to find information to solve this problem.
How should I close express server?
NodeJS configuration notes:
$ node --version
v0.8.17
$ npm --version
1.2.0
$ npm view express version
3.0.6
Actual application code:
var app = express();
// configure Express
app.configure(function() {
// … configuration
});
app.get(
'/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
setTimeout(function () {
app.close();
// TypeError: Object function app(req, res){ app.handle(req, res); } has no method 'close'
}, 3000)
}
);
app.listen('http://localhost:5000/');
Also, I have found ‘nodejs express close…’ but I don't sure if I can use it with code I have: var app = express();.
app.listen() returns http.Server. You should invoke close() on that instance and not on app instance.
Ex.
app.get(
'/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
setTimeout(function () {
server.close();
// ^^^^^^^^^^^
}, 3000)
}
);
var server = app.listen('http://localhost:5000/');
// ^^^^^^^^^^
You can inspect sources: /node_modules/express/lib/application.js
In express v3 they removed this function.
You can still achieve the same by assigning the result of app.listen() function and apply close on it:
var server = app.listen(3000);
server.close((err) => {
console.log('server closed')
process.exit(err ? 1 : 0)
})
https://github.com/visionmedia/express/issues/1366
If any error occurs in your express app then you must have to close the server and you can do that like below-
var app = express();
var server = app.listen(process.env.PORT || 5000)
If any error occurs then our application will get a signal named SIGTERM. You can read more SIGTERM here - https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html
process.on('SIGTERM', () => {
console.info('SIGTERM signal received.');
console.log('Closing http server.');
server.close((err) => {
console.log('Http server closed.');
process.exit(err ? 1 : 0);
});
});
I have answered a variation of "how to terminate a HTTP server" many times on different node.js support channels. Unfortunately, I couldn't recommend any of the existing libraries because they are lacking in one or another way. I have since put together a package that (I believe) is handling all the cases expected of graceful express.js HTTP(S) server termination.
https://github.com/gajus/http-terminator
The main benefit of http-terminator is that:
it does not monkey-patch Node.js API
it immediately destroys all sockets without an attached HTTP request
it allows graceful timeout to sockets with ongoing HTTP requests
it properly handles HTTPS connections
it informs connections using keep-alive that server is shutting down by setting a connection: close header
it does not terminate the Node.js process
calling server.close does the job
server.close((err) => {
console.log('server closed')
process.exit(err ? 1 : 0)
})
also it is good to listen for system(user) signals and shutdown gracefully on them too, for that you should listen on both SIGTERM and SIGINT
const port = process.env.PORT || 5000;
const server = app.listen(port);
console.log(`listening on port:${port}`);
for (let signal of ["SIGTERM", "SIGINT"])
process.on(signal, () => {
console.info(`${signal} signal received.`);
console.log("Closing http server.");
server.close((err) => {
console.log("Http server closed.");
process.exit(err ? 1 : 0);
});
});
Old question but now Node v18.2.0 introduced server.closeAllConnections(). It should be noted that server.close never runs its callback when the browser sends the request Connection: keep-alive, because server.close only stops the server from accepting new connections, it does not close old connections.
Before Node v18.2.0 I tackled this problem by waiting 5 seconds for the server to shutdown, after which it would force exit.
This code encompasses both situations
process.on('SIGINT', gracefulShutdown)
process.on('SIGTERM', gracefulShutdown)
function gracefulShutdown (signal) {
if (signal) console.log(`\nReceived signal ${signal}`)
console.log('Gracefully closing http server')
// closeAllConnections() is only available from Node v18.02
if (server.closeAllConnections) server.closeAllConnections()
else setTimeout(() => process.exit(0), 5000)
try {
server.close(function (err) {
if (err) {
console.error('There was an error', err)
process.exit(1)
} else {
console.log('http server closed successfully. Exiting!')
process.exit(0)
}
})
} catch (err) {
console.error('There was an error', err)
setTimeout(() => process.exit(1), 500)
}
}
Most answers call process.exit(), I don't think this is a good idea. You probably need to perform some teardown, also it's simply not needed.
const server = app.listen(port);
server.on('close', () => {
// Perform some teardown, for example with Knex.js: knex.destroy()
});
// FYI Docker "stop" sends SIGTERM
// If SIGTERM is not catched, Docker is forced to kill the container after ~10s
// and this provokes an exit code 137 instead of 0 (success)
process.on('SIGTERM', () => server.close());
Check Express.js 4.x documentation: https://expressjs.com/en/advanced/healthcheck-graceful-shutdown.html#graceful-shutdown

Categories

Resources