NodeJS: combination of post requests and socket.io - node.js

I'm creating a web application which has two web portals, and a node server. First portal sends HTTP requests(POST) while the second web portal should show them. Then a user will type some text and send it back to the server through the socket and it should redirect it to the first portal. I've used socket.io for the communication happen between the second portal and the server, while the first one does it though post. I'm struggling with finding a way to receive the answer from the second web portal and send the answer back to the first one. Here's the code.
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(5700);
io.on('connection', function (socket) {
console.log("connected" + socket.id);
});
app.post('/server', rawBody, function(req, res, next){
var question = req.query.question;
io.emit('question', {data: question});
io.on('answer', function(data) {
var body = {
response: "data.answer"
};
res.json(body);
});
});
As I've found io.on('answer', function(data) is incorrect. But what I need is something like this:
io.on('answer', function(data) {
var body = {
response: "data.answer"
};
res.json(body);
});
Could you please tell me how should I get the answer back from the socket and upon receive it, send the reply back to the first web portal.

Something like this:
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(5700);
io.on('connection', function (socket) {
console.log("connected" + socket.id);
app.post('/server', rawBody, function(req, res, next){
var question = req.query.question;
socket.emit('question', {data: question});
});
socket.on('answer', function(data) {
var body = {
response: "data.answer"
};
res.json(body);
});
});

Related

Forward request to ws client and wait for response Express

I'm trying to build an endpoint that will receive a request, emit the request data to a WebSocket client, wait for an event, then send back the response using express + socketio. This question is similar to it: Wait for socketio event inside express route
1) Receive request at http://localhost:3000/endpoint
2) Emit the event to web sockets as 'req'
3) Wait for 'res' event from ws
4) Send the received events details as the response of express.
Here is how I'm implemented:
server.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
var socket;
io.on('connection', function (s) {
socket = s;
});
http.listen(3000);
app.get('/endpoint', function (req, res) {
console.log('new request')
io.emit('req', { data: 'hello' });
socket.on('res', function (data) {
res.status(200).json(data);
});
});
index.html
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('req', (data) => {
console.log(data)
socket.emit('res', data);
});
</script>
The script works fine for the first request on /endpoint. But if i hit the url again, it says
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
Please note that:
socket.on('res', function (data) {
res.status(200).json(data);
});
Is being called each time a socket is sending a response, thus showing the above error. You should unbind the listener inside the callback function.
Keep an array of express responses and set an id to each request. So it can be used later and delete if needed.
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var timeout = require('connect-timeout');
var uuid = require('uuidv4');
var _ = require('lodash');
app.use(timeout('10s'));
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
let responses = []
io.on('connection', (socket) => {
socket.on('res', (e) => {
var obj = _.find(responses, r => r.id === e.id);
obj.res.send(e)
_.remove(responses, r => r.id === e.id);
})
})
app.get('/endpoint', (req, res) => {
const id = uuid()
io.emit('req', { id, ip: req.ip, header: req.headers, method: req.method });
responses.push({ id, res })
});
http.listen(3000);
You're trying to do two different async tasks for the same data.
First, take your socket.on('res'...) out of the app.get().
Send back res.status(200) immediately with express to say you received the request and it is processing. Then send the socket message to the client using socket.io when it's complete. You'll want to save the connected users socket client ID and use io.to(socketId).emit(...data...) to do this
the other option is what I always do (assuming it's not a crazy large payload of data you're sending) Just use socket.io for the whole process.
client
function makeRequest () {
socket.on('data-complete--error', function ( error ) {
// ... message to user :(
// also remove these handlers when finished
socket.off('data-complete--error');
socket.off('data-complete--success');
});
socket.on('data-complete--success', function ( data ) {
// ... message to user :)
// ... handle data
// also remove these handlers when finished
socket.off('data-complete--error');
socket.off('data-complete--success');
});
socket.emit('request-data');
}
makeRequest();
server
move your stuff out and handle without using express at all

Why does not sending data io.sockets.emit and socket.broadcast.emit

Tried different methods, but the data is sent to a maximum of one or two clients. How to send data to all the clients connected to the server ? What am I doing wrong?
Server.js:
var PORT = 3000;
var options = {
// 'log level': 0
};
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app);
var io = require('socket.io').listen(server, options);
server.listen(PORT);
app.get('/', function (req, res) {
res.sendfile(__dirname + '/attantions/templates/.default/template.php');
});
io.sockets.on('connection', function (client) {
client.on('attantion', function (data) {
try {
// Tried so
io.sockets.volatile.emit('attantion', data);
// And tried so
io.sockets.emit('attantion', data);
client.emit('attantion', data);
client.broadcast.emit('attantion', data );
} catch (e) {
console.log(e);
client.disconnect();
}
});
});
Client.js:
socket.emit("attantion", data);
socket.on('attantion', function (data) {
pushData(data);
});
See this post for different options for socket.io messages
Send response to all clients except sender (Socket.io)
io.sockets.on('connection', function (client) {
client.on('attantion', function (data) {
//client.emit('attantion', data ); // This will send it to only the client
//client.broadcast.emit('attantion', data); // This will send it to everyone but this client
io.emit('attantion', data); // This will send it to all attached sockets.
});
});
Edit
I wonder if this post can help you?
Socket.io - Cannot load file
I was curious how sending the php file to the client through node.js works? are you using another framework?
Could you show more of what your client code looks like? loading the lib and the instantiation of the socket.

socket.io only working in main server file, not in route files

Version 2 of post
Okay, first the file structure:
app
|___app.js
|___models/
|_user.js
|___routes/
|___admin.js
|___public/
|___js/
|___script.js
app.js:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
http.listen(3000, function (err){
if (err) throw err;
console.log("Server is running");
});
script.js:
var socket = io();
//Next, this tells the browser that is has connected to the socket.io server
socket.on('connect', function() {
console.log('Connected to socket.io server!');
});
socket.on('message', function(message){
console.log('New message: ');
console.log(message.text);
});
var $newUsername = $('#Username');
$newUsername.on('blur', function(event){
socket.emit('message', {
text: $newUsername.val()
});
});
So on a registration page, if a user enters a 'username' already in the database, it will console.log 'This user already exists'. At least, that's the idea.
There is still sockets.io code I need to add on the server side. I am trying to put it here:
admin.js:
var router = require('express').Router();
var User = require('../models/user');
io.on('connection', function(socket) {
console.log('User connected via socket.io!');
socket.on('message', function(message) {
Username.findOne({username: message.text}, function (err, existingUsername) {
if (existingUsername) {
console.log('This user already exists: ' + message.text);
}
});
});
});
So, as this stands, the sockets.io code in admin.js won't work because it can't access the io function. I would like to know how I can fix this.
To add to this: The sockets.io code I have in the admin.js file will work fine if I placed it in my app.js file.
The key here is to pass the io instance to any module that needs it when that module is first loaded. This is called the "push" method of sharing as you share by pushing data from one module to another by passing it in the constructor function of the other module.
There is also a "pull" module where one module asks some other module for some shared data by calling a method in that module.
Here's how you could implement the "push" model:
In your admin.js file, you define a constructor function that you call and pass the io instance to when you load it:
var router = require('express').Router();
var User = require('../models/user');
var io;
// define constructor function that receives the io instance so the rest
// of the module can use it
module.exports = function(ioInstance) {
io = ioInstance;
io.on('connection', function(socket) {
console.log('User connected via socket.io!');
socket.on('message', function(message) {
Username.findOne({
username: message.text
}, function(err, existingUsername) {
if (existingUsername) {
console.log('This user already exists: ' + message.text);
}
});
});
});
}
Then, in your app.js file:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
http.listen(3000, function (err){
if (err) throw err;
console.log("Server is running");
});
// when you load the admin.js file, you pass it the io instance
require('./routes/admin.js')(io);
Below are 2 answers. While both answers will technically work, it is advised that you do not use either of them. Use the answer provided above.
Answer 2
So my route files all have this:
var router = require('express').Router();
Now, in any of the route files where I need sockets.io I did this:
var http = require('http').Server(router);
var io = require('socket.io')(http);
And this got my sockets.io code working.
Answer 1
So to get sockets.io code working in my route files, I simply removed 'var' in front of my http and io variables.
I changed:
//Socket io config
var http = require('http').Server(app);
var io = require('socket.io')(http);
to this:
//Socket io config
http = require('http').Server(app);
io = require('socket.io')(http);
Now my sockets.io code in my route files work fine.

How to emit event in socket.io based on client?

I am working on realtime data visualization application using node.js, express and socket.io.
Requirement:
Have to emit the events based on the client request.
For example: If user enter the url as http://localhost:8080/pages socket.io should emit the topic pages to client and another user request for http://localhost:8080/locations socket should emit location to that particular user.
Code
var server = app.listen("8080");
var socket = require('socket.io');
var io = socket.listen(server);
var config = {};
io.on('connection', function (socket) {
config.socket = io.sockets.socket(socket.id);
socket.on('disconnect', function () {
console.log('socket.io is disconnected');
});
});
app.get('/*', function(req, res) {
var url = req.url;
var eventName = url.substring('/'.length);
//pages and locations
config.socket.volatile.emit(eventName, result);
});
Client Code:
//No problem in client code.Its working correctly.
Sample code as follows
socket.on('pages', function (result) {
console.log(result);
});
Problem:
It is emitting pages and locations to both the clients.
Any suggestion to overcome this problem.
I couldn't understand your approach on this, but because you said you're rendering different pages, It means you can serve different code, so what about doing it like this:
Server Side:
var server = app.listen("8080");
var socket = require('socket.io');
var io = socket.listen(server);
var config = {};
app.get('/pages', function(req, res) {
res.render('pages.html');
});
app.get('/locations', function(req, res) {
res.render('locations.html');
});
io.on('connection', function (socket) {
socket.on('pagesEvent', function(data){
socket.volatile.emit('pages', {your: 'data'});
});
socket.on('locationsEvent', function(data){
socket.volatile.emit('locations', {your: 'data'});
});
});
On Client side:
pages.html:
socket.on('connect', function(){
socket.emit('pagesEvent', {});
});
socket.on('pages', function(data){
// do stuff here
});
locations.html:
socket.on('connect', function(){
socket.emit('locationsEvent', {});
});
socket.on('locations', function(data){
// do stuff here
});
You are doing it wrong, WebSockets supposed to work same in both directions. Client emit event to Server, server emit back to Client/Subscribers.
The way you are doing things, seems like a way of implementing API, but for some reason you are trying to implement it with WebSockets, instead of XHR.

node.js with express routing to a new page within socket.io

I have a button on a page that when it is clicked, i want the page to call some node.js server code. I have this wired up via socket.io. The issue I am running into is I need the socket.io code to redirect my web page based on some business logic. Not sure what is the best way to do that via express (res.redirect?) and also how to get access to that within the socket.io call. Any help would be appreciated!
App.js
var dashboard = require('./middleware/dashboard.js');
...
app.get("/dashboard", function (req, res) {
dashboard.show(req, res);
});
...
var http = require('http');
var server = http.createServer(app);
var io = require('socket.io').listen(server);
io.set('log level',2); // sets socket io log level 0=error, 1=warn, 2=info, 3=debug
...
dashboard.wireUpSocketIO(io);
...
dashboard.js
var savedResponse;
exports.show = function(req, res) {
savedResponse = res;
res.render("dashboard.jade"});
};
exports.wireUpSocketIO = function(io){
io.sockets.on('connection', function (socket) {
socket.on('dashboardOnButtonClick', function(msg) {
<biz logic>
savedResponse.render("someOtherPage.jade", {
locals: {
title: "someOtherPage",
filter: msg.filter
}
});
});
});
}
dashboard.jade
button#btnFilter(class='btn btn-info') Test Button
script(src="/socket.io/socket.io.js")
script .
var socket = io.connect('http://localhost');
var emitMessage = { blah: false, filter:"n/a"};
$('#btnFilter').click(function(){
emitMessage.blah = true;
emitMessage.filter = "filterByBlah";
socket.emit('dashboardOnButtonClick', emitMessage);
});
I could think it is easier to do from the client side, that is, javascript:
Once the server side logic is done, you send a message back to the client and change the window.location to the new one.
Once the server is done, the client can issue a request to that the server will respond with a HTTP 301/302 Redirect. This way you can call res.Redirect on that Express request.
Regards,

Resources