Express-session uses Redis. When Redis stops, the request is blocked - express

In NestJS, I use Express-Session and use Redis as Store.
The Redis address is a load balancing address. When I offline the Redis server, I sent a new request and found that I had been loading until timeout. I want to return the response after the redis error is detected.
const RedisStore = connectRedis(session);
const redisClient = new Redis('redis://****:6379');
redisClient.on('error', (err) => {
console.error(err);
});
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: 'asdasdasd',
resave: false,
saveUninitialized: false,
cookie: { httpOnly: true, maxAge: 1000 * 60 * 10 },
}),
);
redis error
Error: connect ETIMEDOUT
at Socket.<anonymous> (/Users/code/nestjs-session/node_modules/ioredis/built/Redis.js:168:41)
at Object.onceWrapper (events.js:312:28)
at Socket.emit (events.js:223:5)
at Socket._onTimeout (net.js:474:8)
at listOnTimeout (internal/timers.js:531:17)
at processTimers (internal/timers.js:475:7) {
errorno: 'ETIMEDOUT',
code: 'ETIMEDOUT',
syscall: 'connect'
}

try with redis legacy mode
const redis = createClient({ socket: { host: "localhost", port: 6379 }, legacyMode: true });

Related

Socket.io server TransportError

I'm facing to an issue with a Socket.io server running throw an Express.js server with Next.js.
The server send error without any client connected. But clients can connect from browser without any issue...
Here is the error on console where the server is running :
Socket error TransportError: xhr poll error
at Polling.onError (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transport.js:46:37)
at Request.<anonymous> (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transports/polling.js:255:18)
at Request.Emitter.emit (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/#socket.io/component-emitter/index.js:143:20)
at Request.onError (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transports/polling.js:356:14)
at Timeout._onTimeout (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transports/polling.js:329:30)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7) {
description: 0,
context: XMLHttpRequest {
UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4,
readyState: 4,
onreadystatechange: [Function (anonymous)],
responseText: 'Error: getaddrinfo ENOTFOUND undefined\n' +
' at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:71:26)',
responseXML: '',
status: 0,
statusText: Error: getaddrinfo ENOTFOUND undefined
at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:71:26) {
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'undefined'
},
open: [Function (anonymous)],
setDisableHeaderCheck: [Function (anonymous)],
setRequestHeader: [Function (anonymous)],
getResponseHeader: [Function (anonymous)],
getAllResponseHeaders: [Function (anonymous)],
getRequestHeader: [Function (anonymous)],
send: [Function (anonymous)],
handleError: [Function (anonymous)],
abort: [Function (anonymous)],
addEventListener: [Function (anonymous)],
removeEventListener: [Function (anonymous)],
dispatchEvent: [Function (anonymous)]
},
type: 'TransportError'
}
socket.io-client:manager reconnect attempt error +1ms
socket.io-client:manager will wait 5000ms before reconnect attempt +0ms
Just to be clear, this error is not on client side but on the server, clients from browser have a working connection even with this error from the server. The client does not get any error and works fine.
Here is my express.js server code :
import express, {Express} from 'express';
import * as http from 'http';
import next, {NextApiHandler} from 'next';
import passport from 'passport';
import session, {SessionOptions} from 'express-session';
import {Options} from "session-file-store";
const FileStore = require('session-file-store')(session);
import uid from 'uid-safe';
import bodyParser from 'body-parser';
import routes from './routes';
import SocketServer from "../socketio/SocketServer";
import socketMiddleware from "../socketio/SocketMiddleware";
const port: number = parseInt(process.env.PORT || '3000', 10);
const dev: boolean = process.env.NODE_ENV !== 'production';
const nextApp = next({ dev });
const nextHandler: NextApiHandler = nextApp.getRequestHandler();
nextApp.prepare().then(() => {
const app: Express = express();
const server: http.Server = http.createServer(app);
// Socket io server
const socketServer = new SocketServer(server);
// Session
const fileStoreOptions: Options = {
path: './.next/session' // register session folder in .next folder
};
const sessionConfig: SessionOptions = {
//secret: uid.sync(18),
genid: (req) => {
return 'app_' + uid.sync(18) // use UUIDs for session IDs
},
secret: 'SECRET',
cookie: {
maxAge: 43200 * 1000 // 12h
},
resave: false,
saveUninitialized: true,
store: new FileStore(fileStoreOptions)
};
app.use(bodyParser.json());
app.use(session(sessionConfig));
passport.serializeUser((user: any, done: (a: any, b: string) => void) => done(null, user));
passport.deserializeUser((user: string, done: (a: any, b: any) => void) => done(null, user));
app.use(passport.initialize());
app.use(passport.session());
// Add socketio to request to have available in express routes
app.use(socketMiddleware(socketServer));
app.use('/api/auth', routes.security); // anonymous
app.use('/api/users', routes.users); // authenticated
app.use('/api/patients', routes.patients); // authenticated
app.use('/api/appointments', routes.appointments); // authenticated
app.use('/api/consultations', routes.consultations); // authenticated
app.use('/api/tasks', routes.tasks); // authenticated
app.use('/api/documents', routes.documents); // authenticated
app.use('/', routes.nextjs); // anonymous and authenticated
// Handle all nextjs route
app.all('*', (req: any, res: any) => nextHandler(req, res));
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
And then my SocketServer.ts
import {Server, Socket} from "socket.io";
import * as http from 'http';
import {ServerEvents, ClientEvents} from "./SocketEvents";
class SocketServer {
private io: Server;
public constructor(server: http.Server) {
this.io = new Server(server);
this.initServer();
}
private initServer = (): void => {
this.io.on(ServerEvents.CONNECTION, (socket: Socket) => {
this.initClient(socket);
});
}
private initClient = (socket: Socket): void => {
console.log('New client connected with id', socket.id);
// Emit status message for new connected client
socket.emit(ServerEvents.STATUS, 'Connected on Gynemanager SocketIO server');
socket.on(ClientEvents.USER_CONNECTED, async (socketId, username) => {
const sockets = await this.io.fetchSockets(); // Get all connected clients
// Add username to socket data
sockets.forEach(socket => {
if (socket.id === socketId) socket.data.username = username; // add username to new connected client
})
})
socket.on(ServerEvents.DISCONNECT, (reason: string) => {
console.log('Client disconnected', reason);
})
}
}
export default SocketServer
And then the SocketMiddleware.ts
import {Response, NextFunction} from 'express';
import SocketServer from "./SocketServer";
import {CustomRequest} from "../types/express";
const socketMiddleware = (socketServer: SocketServer) => {
return (req: CustomRequest, res: Response, next: NextFunction) => {
req.socketServer = socketServer;
next();
}
}
export default socketMiddleware;
Any help will be appreciate.
Finally I figure out what was my problem.
I initialised the socket.io connection from _app.tsx for clients because this is the place where I use React context provider to bind my SocketClient.ts to the whole application.
To fix the issue I have to move the connection initialisation from _app.tsx to another page (for my case from the page redirected from login page), and check if connection is already init on the other page where socket is also needed to avoid multi connection and then everything works as expected.

Saving an express-session in socket.io

I can not understand why my sessions do not want to be saved.
Each time I connect, I get a new session id, but if I just go (through the browser) to tessocket.local, the session is saved and already when called through the socket, it is normally determined. That's actually the question - how to defeat it?
Forcing the session to be saved after io.on("connection" doesn't help.
If you remove transports: ["websocket"] - polling starts working, but then the session is not defined at all through the socket.
client.js
const socketOptions = {
"force new connection" : true,
"reconnectionAttempts": "Infinity",
"timeout" : 10000,
"transports" : ["websocket"]
};
// #ts-ignore
socket = io('https://tessocket.local',socketOptions)
// #ts-ignore
socket.onAny((event, ...args) => {
console.log(event, args);
});
return socket
server.js
const app = require('express')(),
http = require('http'),
https = require('https'),
// http = require("http"),
fs = require( "fs" ),
path = require("path"),
eSession = require("express-session"),
MongoDBStore = require('express-mongodb-session')(eSession),
store = new MongoDBStore({
uri: 'mongodb://admin:admin#localhost:27017/tmp?authSource=admin',
collection: 'sessions'
});
options = {
key: fs.readFileSync(path.resolve(__dirname, "../minica/test.local/key.pem")),
cert: fs.readFileSync(path.resolve(__dirname, "../minica/test.local/cert.pem")),
},
server = https.createServer(options, app)
const io = require("socket.io")(server, {
cors: "http://test.local:3000",
transports: ["websocket"]
}),
session = eSession({
secret: "my-secret",
resave: true,
saveUninitialized: true,
store,
cookie: {
httpOnly: false, // key
maxAge: null,
path: "/",
secure: true,
sameSite: 'none'
},
}),
sharedsession = require("express-socket.io-session");
// Attach session
app.use(session);
// Share session with io sockets
io.use(sharedsession(session));
io.on("connection", function(socket) {
// Accept a login event with user's data
console.log(socket.handshake.session.id)
socket.on("login", function(userdata) {
socket.handshake.session.userdata = userdata;
socket.handshake.session.save();
});
socket.on("logout", function(userdata) {
if (socket.handshake.session.userdata) {
delete socket.handshake.session.userdata;
socket.handshake.session.save();
}
});
});
server.listen(443);
socket.io#4.4.1
express#4

Axios get request not returning when express session uses connect-redis store

So I have this weird thing going on where the axios requests I make from the front end do not get the response I'm sending from the server, but it only happens when I set the store in express session.
I am making a post request to the login route like in this function
const logIn = async (formData) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const response = await axios.post("/login", formData, config);
// do something with the response
};
When I make the request the server responds with status 200 and a user object. When I check the network tab in the browser devtools, I see that it is getting the correct response and the payload is showing the right user object as well
However axios never returns and is left hanging forever.
Now here's the kicker. This only happens if I set the store on the express session in my app.js
import express from "express";
import router from "./routes/router.js";
import session from "express-session";
import "dotenv/config";
import redis from "redis";
import connectRedis from "connect-redis";
const PORT = process.env.PORT || 5000;
const app = express();
const redisClient = redis.createClient();
redisClient.connect();
redisClient.on("connect", () => {
console.log("redis client connected");
});
const RedisStore = connectRedis(session);
app.use(
session({
name: "qid",
store: new RedisStore({ client: redisClient }), // RIGHT HERE
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365,
httpOnly: true,
sameSite: "lax",
secure: false,
},
saveUninitialized: false,
secret: process.env.SESSION_SECRET,
resave: false,
})
);
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use("/", router);
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
As soon as I comment out the line where I set the store, Axios gets the response and everything works perfectly. So I have to assume that the session store is somehow blocking the server response from getting to axios.
Do you have any idea why that might be?
I ran into the exact same issue, but I could finally solve this for my setup by using ioredis instead of redis.
I updated your app.js accordingly
import express from "express";
import router from "./routes/router.js";
import session from "express-session";
import "dotenv/config";
import Redis from 'ioredis';
import connectRedis from "connect-redis";
const PORT = process.env.PORT || 5000;
const app = express();
const redisClient = new Redis();
redisClient.on("connect", () => {
console.log("redis client connected");
});
const RedisStore = connectRedis(session);
app.use(
session({
name: "qid",
store: new RedisStore({ client: redisClient }), // RIGHT HERE
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365,
httpOnly: true,
sameSite: "lax",
secure: false,
},
saveUninitialized: false,
secret: process.env.SESSION_SECRET,
resave: false,
})
);
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use("/", router);
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

how to connect to redis cluster using multiple node connection string?

I am trying following code but not working. Getting error
[ioredis] Unhandled error event: ClusterAllFailedError: Failed to
refresh slots cache.
const Redis = require("ioredis");
const cluster = new Redis.Cluster([
{
port: 6379,
host: "172.x.x.x",
},
{
port: 6379,
host: "172.x.x.x",
},
{
port: 6379,
host: "172.x.x.x",
},
], {
redisOptions: {
password: "ssffvggfg",
},
});
cluster.set("foo", "bar");
cluster.get("foo", (err, res) => {
console.log(res);
});
Thanks

"Client network socket disconnected before secure TLS connection was established" error in nodemailer

I want to setup contact form, that's why I am using node mailer and postman for testing purpose but, i am receiving this error
Client network socket disconnected before secure TLS connection was established
Here is my code:
const transporter = nodemailer.createTransport({
host: 'smtp.itcarver.com',
port: 587,
secure: false,
auth: { user: 'support#itcarver.com', pass: 'xyz' },
tls: { rejectUnauthorized: false }
});
const mailOptions = {
from: 'support#itcarver.com',
to: "xyz",
subject: "subject",
text: "test",
};
transporter.sendMail(mailOptions, function(error, info) {
if (error) {
console.log(error)
res.send("error");
} else {
res.send('Email sent: ');
}
});
And here is the error:
Error: Client network socket disconnected before secure TLS connection was established
at connResetException (internal/errors.js:604:14)
at TLSSocket.onConnectEnd (_tls_wrap.js:1513:19)
at Object.onceWrapper (events.js:417:28)
at TLSSocket.emit (events.js:323:22)
at endReadableNT (_stream_readable.js:1204:12)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
code: 'ESOCKET',
path: undefined,
host: 'smtp.itcarver.com',
port: undefined,
localAddress: undefined,
command: 'CONN'
}
You may need to set tls maxVersion to some other than default (which is TLSv1.3). It was necessary in my case.
tls: { maxVersion: 'TLSv1.2' }