Express middleware not capturing request events for socket.io - javascript

I'm using Express (v4.17.3), Socket.io, and Node.Js's http module. I'm adding a middleware for express to capture all incoming requests but that's failing.
I'll first show the code I'm using and the output then explain my understanding/expectation of the output (I'm new to Node and all the mentioned libraries so perhaps I'm missing something)
First of all, below is the code I'm referring to. Using Express's middleware I'm trying to capture all the incoming requests and log them, and doing the same for the http on("request"). However, requests going to socket.io aren't captured by the middleware.
// Express
const express = require("express");
const app = express()
// Socket
const server = require('http').createServer(app);
const {Server} = require("socket.io");
const io = new Server(server)
// Want to listen to all incoming requests using the middleware (this doesn't work)
app.use((req,res,next)=>{
console.log(`Express request = ${req.url}`)
next()
})
// Listening to all incoming requests (this works)
server.on("request", (req, res)=>{
console.log(`Http request = ${req.url}`)
})
server.listen(8080, () => {
console.log(`Listening on port 8080`)
})
output when I GET /
Express request = /
Http request = /
Http request = /socket.io/socket.io.js
Http request = /socket.io/?EIO=4&transport=polling&t=O0va...
Http request = /socket.io/?EIO=4&transport=polling&t=O0va24A&sid=c...
Http request = /socket.io/?EIO=4&transport=polling&t=O0va24F&sid=c...
Http request = /socket.io/?EIO=4&transport=polling&t=O0va27x&sid=c...
My expected output is to have equal logs for the middleware app.use() and on("request") ("Express request = " & "Http request = ")
My understanding:
1- When I add a middleware for express as in the code below, any incoming requests should be captured here first before going anywhere else. (correct?)
app.use((req,res,next)=>{...})
2- When I'm passing the express app as an argument to http'screateServer, that the express app will be treated as a listener and any request events will be passed to it. (correct?)
const server = require('http').createServer(app);
So if my understanding is correct, why aren't all the requests captured by the request event passed to the middleware as well?

This is normal. Socket.io puts itself in front of express (or any other listener for incoming requests on the http server) so that it takes the request before Express sees it. Thus, Express (or it's middleware) never see any socket.io connection requests.
Socket.io has its own middleware layer that you can use to participate in the initialization of socket.io requests.
Or, you can register for incoming socket.io connections (to be called after they are already connected) with the io.on('connection', ...) event handler.
When I add a middleware for express as in the code below, any incoming requests should be captured here first before going anywhere else. (correct?)
That is true except for code that registers directly request handlers right on the http server and inserts itself before Express in the listener chain, thus preventing Express from seeing any requests that are destined for socket.io.
When I'm passing the express app as an argument to http'screateServer, that the express app will be treated as a listener and any request events will be passed to it. (correct?)
That is true. But socket.io jumps in front of Express and takes/hides any requests it wants so that Express never sees them.
If you're curious, here's the socket.io code that jumps in line to the front of all listeners for the http server thus bypassing the express listener:
attachServe(srv) {
debug("attaching client serving req handler");
const evs = srv.listeners("request").slice(0);
srv.removeAllListeners("request");
srv.on("request", (req, res) => {
if (this.clientPathRegex.test(req.url)) {
this.serve(req, res);
}
else {
for (let i = 0; i < evs.length; i++) {
evs[i].call(srv, req, res);
}
}
});
}
It grabs all the existing listeners into an array. Then, it removes them all. Then, it registers for the request event itself and, if it is a socket.io request, then it does not call the prior listeners. If it is not a socket.io prefix, then it manually calls the prior listeners.

I believe you need to log the messages sent on socket.
io.on('connection', (socket) => { socket.on('chat message', (msg) => { console.log('message: ' + msg); }); });

Related

Emitting inside express route (using socket.io-client)

I recently started refactoring some code to separate front and back end which I previously made into one combined service.
I've now got a problem where I need the web server to emit to the data server (after validation). I previously managed this by calling the emit from inside the express route after adding this code
var io = require('socket.io')(server);
app.io = io;
app.use(function(req, res, next) {
req.io = socket;
next();
});
// Then inside of the route
app.post('/make', function(request, response) {
if (request.session.loggedin = true) {
var sio = request.io;
var to = request.body.to;
var amt = request.body.amt;
var from = request.body.from;
if (to.length > 0 && amt != 0) {
if (amt <= request.session.balance) {
sio.emit('send-transactions', {"to": to, "from": from, "amt": amt});
Now that these are separate, the web server is just connecting as client (using socket.io-client) so I can't seem to get it to work.
I'm using,
var socket = require('socket.io-client')('wss://ip:port', {secure:true,rejectUnauthorized:false, reconnect:true});
I tried changing all references of "io" to "socket" as my vars are different but I can't figure out if it's the client library that isn't capable of doing so .
Tl;dr - I am trying to emit from web server (technically client) to back end (socket.io server) to fire the event which would then broadcast to all clients.
Just wanted to close this one off so here was the solution:
As my routes were previously intended for server to emit to all clients, using the same route as a client was not being fired on the server side. I just needed to change my route to the server side one and the full sequence is now complete and emitting to that route then gets emitted to all clients

How To Display Websocket Response To Browser In NodeJS

I am new to NodeJS. My code is
const webSocket= require('ws');
const express =
require('express');
const app=express();
Var url = "wss://stream.binance.com:9443/BTCUSDT#trade`"
const ws = new webSocket(url);
app.get('/',(req,res)=>{
ws.on('message',data=>{
res.send(data)
})
})
app.listen(3000)
When i use console.log instead of res.send() it works successfully and consoled the data.
How can i display these dynamic data to a browser. Thanks In Advance.
Websocket is event-driven
The syntax for a event emmiter on the server which emit the event will be like the following:
ws.emit('eventName',data);
The syntax of a event listener which receives a message will be like the following:
//For the client to receive the message, you should put something like this at the front end
ws.on('eventName',data=>{
const content = data;
document.getElementById('chat').innerHTML += content
});
Websocket is a different protocol than HTTP. It uses HTTP established the connection. The connection will then be upgraded to websocket. By the time, you shouldn't HTTP to send data anymore like res.send() but instead use ws.emit('eventName', data) for example.
Websocket Protocol

EPIPE on net.connect to an HTTP server

I am trying to connect to a node http server socket (expressJs) with net.connect in roder to pass that socket to my repl to be able to basically connect to my http server and launch commands.
when trying this I got the error EPIPE the second I started the repl.
here is the code for the repl:
const args = process.argv.slice(2);
if (args.length < 1) {
process.exit(1);
}
const url = args[0];
const [host, port] = url.split(':');
//this will get the url to connect to
const socket = net.connect(parseInt(port), host);
process.stdin.pipe(socket);
socket.pipe(process.stdout);
Console.start({
expose: { container, Metric:metricsObject},
socket:socket
});
The start function :
start(options = {}) {
const { expose, socket } = options;
const repl = REPL.start({
eval: promisableEval,
terminal:true,
input: socket,
output: socket,
});
Object.assign(repl.context, expose);
}
The http server running :
const http = this.express
.listen(this.config.web.port, () => {
const { port } = http.address();
this.logger.info(`[p ${process.pid}] Listening at port ${port}`);
resolve();
});
this.express is just an instance of express : this.express = express();
It looks like you're trying to connect to an http (or https?) server at a URL like http://mine.example.com:3000/path/item by saying
net.connect(parseInt('3000/path/item'), 'http://mine.example.com');
It won't work for a number of reasons.
Unless you're a pretty good programmer expert at the http protocol, you should not use net.connect to talk to http servers. Try using http.clientRequest instead.
hostnames passed to net.connect should be raw machine names like 'mine.example.com' and not preceded by a protocol specifier.
ports, similar.
Sorry, I don't get what you're trying to do with stdin and stdout. But your socket would not be ready for use until its connect operation completes and you get an event announcing that.
You can use the old telnet program to connect to an http server. It lets you type stuff to the server, and then displays what you get back. In your case you'd do
telnet localhost 3000 # from shell your command line
The server then connects and sits there waiting. You type
GET / HTTPS/1.0
and then two <Enter>s. The server then sends back whatever it would send if you put http://localhost:3000 into a browser location line. It then closes the connection.
I'm not sure how that http protocol operation fits into your REPL.
The example you mention at https://medium.com/trabe/mastering-the-node-js-repl-part-3-c0374be0d1bf doesn't connect to an http server, it connects to a tcp server. Http servers (including all node/express servers) are a subspecies of tcp server, but they layer the http protocol on the tcp protocol. The http protocol isn't suitable for the back-and-forth conversational style of REPLs.

Why is nodemailer sending duplicate emails?

So this is a very odd question and I don't expect any one to really have an answer to this, but I'm here to try and see if anyone has experienced the same issue.
The issue that I'm noticing is that our application seems to be sending duplicate emails. For example, I can send a report from our application, and it will send that email once, and then it looks like another one gets sent exactly a minute later.
I'm using nodemailer to send the emails from our applications server, and our default email that we use in our office is Outlook v16.0.12130.20272 using IMAP. These emails are being sent by our noreply email which I believe is being hosted through GoDaddy.
I've sent test emails myself and looked in the network tab to see if it might be a timeout issue, but the response completes with a 200 OK status and the timing shows up as completed as well. Also when I console log the response it only occurs once, which makes me believe that it is actually only sending one email. There must be something happening in between when the host sends the email, and when our recipients actually receive them, but I'm not quite sure.
Here is the server.js file. This is where the smtp request is being made.
var nodemailer = require("nodemailer");
const path = require('path');
const express = require('express');
const http = require('http');
const fs = require('fs');
const socketIO = require('socket.io');
const bodyParser = require('body-parser')
import env from '../env.config.json';
const PORT = require('../env.config.json').SERVER.PORT;
const publicPath = path.join(__dirname, '../public');
import api from './routers/api-routing';
//---------------------------------------------------------------------------
var smtpTransport = nodemailer.createTransport({
service: env.EMAIL.SERVICE,
host: env.EMAIL.HOST,
auth: {
user:env.EMAIL.AUTH.USER,
pass:env.EMAIL.AUTH.PASS
}
});
var mailCounter = 0;
var numPeople = 0;
var app = express();
var server = http.createServer(app);
const port = PORT || 3000;
app.use(express.static(publicPath));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true}));
const io = socketIO(server);
app.use('/api', api(app, io));
// require('./routers/api-routing')(app, io);
//$.get("/send", { to: to, subject: subject).value, text: 'html' }, function (data) { });
app.get('*', function (request, response) {
if (request.get('x-auth')) console.log("x-auth: ", request.get('x-auth'));
const proto = request.get('X-Forwarded-Proto');
if (proto) {
if (proto === 'http') response.redirect(301, "https://ourdomain.com".concat(request.url));
}
response.sendFile(path.resolve(__dirname, '../', 'public', 'index.html'))
if ((request.url).substring(0, 5) == "/send") {
var mailOptions = {
to: request.query.to,
bcc: request.query.bcc,
subject: request.query.subject,
text: request.query.text
}
//console.log(mailOptions); Read up on NodeMailer for details.
smtpTransport.sendMail({ //email options
from: "COMPANY <noreply#ouremail.com>", // sender address. Must be the same as authenticated user if using Gmail.
to: mailOptions.to,
bcc: "COMPANY <noreply#ouremail.com>", // sending to itself
subject: mailOptions.subject, // subject
html: mailOptions.text, // body
}, function (error, response) { //callback
if (error) {
console.log(error);
} else {
console.log("Message sent");
//console.log("Amount of people getting this email: " + response.accepted.length);
}
smtpTransport.close(); // shut down the connection pool, no more messages. Comment this line out to continue sending emails.
});
}
});
io.on('connection', (socket) => {
require('./middleware/sockets')(socket);
});
server.listen(port, () => {
console.log(`Server is up on port ${port}.`);
});
This is the part of our env.config.file that pertains to the emails.
"EMAIL": {
"SERVICE": "Godaddy",
"HOST": "smtp.gmail.com",
"AUTH": {
"USER": "noreply#ouremail.com",
"PASS": "OURPASS"
}
}
If anyone has any ideas or suggestions, I would be very appreciative, thanks!
Your email is being sent on any request sent to your server so if you access it via a browser, the browser will send two requests, one for the path requested and one for the favicon.ico and you're also sending an email when /favicon.ico is requested.
This can happen because you're route handler is configured as:
app.get('*', ...);
That means you're attempting to send an email for every single incoming http request regardless of path.
So, if you go to your host with a browser at http://yourdomain/, it will first request / and then the browser will request /favicon.ico, causing you to then send a second email.
My suggestion is to change from this:
app.get('*', ...);
to this:
app.get('/', ...);
or, even more specific such as:
app.get('/sendemail', ...);
So, you are only sending the email on one specific path request and it will not send the email no other requests such as the favicon. You probably want to add a generic express 404 handler for any other routes.
Note: In a REST design, you would probably send an email with a POST request, not a GET request. A GET would retrieve a resource in a read-only kind of way that does not change any state so it wouldn't have a side effect of sending an email. Note: this isn't related to your problem at all, just a comment about a typical REST design.
After some time I have finally figured out the reason for this behavior. The issue is partially related to what jfriend00 had posted. I ended up making a separate route handler for the emails themselves so that it didn't interfere with the main route handler. The problem is that every request will still go through that route since it is looking for any request indicated by the * and if a person is on the http route making the request instead of https, then it creates a second request or in my case a second email.
You can see that happening in this line here:
if (proto) {
if (proto === 'http') response.redirect(301, "https://ourdomain.com".concat(request.url));
}
The actual fix for this was to create a separate route handler for the emails themselves and then configure my nginx server to reroute to https if a person was going to the http route of the application instead. After this, we have not experienced anymore duplicate emails.
Another way of doing it would be to remove the ```*```` route handler completely and setup the other routes separately. Hopefully this will help somebody in the near future.

Why isn't socket.io callback being triggered inside an express route

I've got the following setup (important bits only for brevity):
app.js
...
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);
server.listen(port, function() {
console.log(`Server is listening on port: ${port}`);
});
io.on('connection', function (socket) {
console.log('connection');
});
const routes = require('./routes/index')(io, passport);
app.use('/', routes);
index.js (server)
router.get('/game/:id', isAuthenticated, (req, res) => {
if (req.id)
{
var game = Game.findOne({_id: req.id}, (err, obj) => {
io.on('getGameInfo', (socket) => {
io.emit('gameInfo', obj);
});
res.render('game', obj);
});
}
else
{
// Id not valid, do something
}
});
client:
const socket = io('http://localhost:3000');
socket.on('gameInfo', function(data) {
console.log(data);
}.bind(this));
socket.on('connect', () => {
socket.emit('getGameInfo');
});
So basically I want to emit a getGameInfo call once I know the client has connected, and the getGameInfo listener has been set up in the game route. But when I emit the getGameInfo from the client, the server callback isn't being hit. I'm not sure if I'm missing something obvious, or if this is a closure issue, or if I'm just having one of those days, or if I'm going about this entirely the wrong way.
There are multiple problems here. I'll start by showing the correct way to listen for an incoming socket.io message on the server:
io.on('connection', function (socket) {
// here's where you have a new socket and you can listen for messages
// on that socket
console.log('connection');
socket.on('gameInfo', (data) => {
socket.emit('gameInfo', obj);
});
});
Some of the issues:
On the server, you listen for messages via the socket object, not via the io object. So, you would typically add these event listeners in the io.on('connection', ...) handler because that's where you first see newly connected sockets.
You pretty much never want to add event listeners inside an Express route handler because that is called many times. In addition, at the moment the route handler is called, the browser has not yet received the page and will not yet be connected so even if this was an OK place to do stuff, the page is not yet connected anyway.
When you want to send a message back to just one connection, you send it with socket.emit(), not io.emit(). io.emit() broadcasts to all connected clients which I don't think is what you want.
I'd suggest you not overload the same message name for client and server to mean two different things as this can lead to confusion when reading code or if you ever share some code between client and server. You client is really sending a "getGameInfo" message and then your server responds with a "gameInfo" message that contains the gameInfo.
If, in a route handler, you want to .emit() to the socket from that page which it looks like you are trying to do, then you have to do some work to create a link between the session of the current page and the socket for that page. There are a number of ways to do that. If you're using any session middleware, you can record the socket in the session at the point the socket connects. Then, from your express routes, you can get that socket from the session object at any time.

Categories

Resources