Wait for socketio event inside express route - javascript

I'm using express,socketio and socketio-client in my application.
(I not very comfortable with nodejs stack...)
to summarize my application flow :
Client => node/express API + Socketoi server <=> nodejs (Socketio-client)
browser send request to a nodejs/express (route /api)
Do some request headers overwrites with middlewares
In the route '/', server sends an emit to a nodejs (Socketio-client)
after executing some logic, socketio-client emit an event with the logics result
I need this result to be send in the response to the client
My code below:
router.get('/*', function (req, res) {
//emit data for socketio-client to apply some logic
app.io.sockets.emit('req', {
reqheader : req.headers,
requrl : req.protocol + "://" + req.headers.host + req.url,
reqmethod : req.method
});
console.log("after emit");
//I use callback to make response wait for socketio server to catch event from client
waitforevent(req, res, function (__res) {
console.log("callback" );
res.end(__res.body);
res.sendStatus(__res.statusCode);
//res.end();
});
function waitforevent(req, res, callback) {
console.log("waiting for event" );
app.io.__socket.on('respp', function (data) {
//console.log("no response yet \n" + JSON.parse(data) );
__res = JSON.parse(data);
console.log("event catched...");
callback(__res);
});
}
});
My problem :
This works only the first time I send a Get http://localhost:3000/api frome the browser. __res.body is printed in the browser.
req 1
after emit
waiting for event
event catched...
callback
Error: Can't set headers after they are sent.
**GET /api 200 73.841 ms - -**
req 2
after emit
waiting for event
Next request will just wait for server to respond, which is, I suspect, not happening because the app.io.__socket.on('respp', function (data){...} is never catched by the server.
After sending more request (while the others are waiting), I noticed this warning in server logs:
(node) warning: possible EventEmitter memory leak detected. 11 respp listeners added. Use emitter.setMaxListeners() to increase limit.
Is there other ways to catch events in a route before sending response to clients?

I solved with once:
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var socket;
io.on('connection', function (sock) {
console.log('Connected');
socket = sock;
});
server.listen(3000);
app.get('/*', function (req, res) {
socket.once('event', function (data) {
if (data.error) {
console.log('is an error');
res.status(400).json(data);
} else {
console.log('is ok');
res.status(200).json(data);
}
});
io.emit('ask-for-event', { data: data });
});

You could remove the event listener when the socket closes to avoid the event listener leak:
router.get('/*', function (req, res) {
app.io.sockets.emit('req', {
reqheader : req.headers,
requrl : req.protocol + "://" + req.headers.host + req.url,
reqmethod : req.method
});
req.socket.on('close', function() {
app.io.__socket.removeListener('respp', resppHandler);
});
app.io.__socket.on('respp', resppHandler);
function resppHandler(data) {
data = JSON.parse(data);
res.statusCode = data.statusCode;
res.end(data.body);
}
});
I'm not sure if app.io.__socket should really be app.io.sockets or not, but I copied it as-is from your code, assuming you know what you're doing.
Additionally, you may wish to add some sort of timeout so as not to keep the request waiting indefinitely.

Related

How to make this websocket real time?

I'm integrating websockets with express on the backend and using browser's native websocket api on the client side. I have so far been able to send and receive message from the client to server and server back to client. But all this happens with a page refresh only. Isn't websocket supposed to be real time? Lets say I make a change in the message on server file, then it has to immediately reflect in my browser's console. and lets say I make a change in the message in the script file on the client side, then it has to immediately show the changes on server's console.(Also I'm using nodemon to run the server so changes has to reflect pretty quickly). But right now, I see myself making a request to / via page refresh and then server upgrading and then responding back with the message.
Please tell me if I'm missing something in the code or otherwise in the concept?
app.js
const express = require('express')
const app = express()
const path = require('path')
const WebSocketServer = require('websocket').server;
app.set('view engine', 'html');
app.use(express.static(path.join(__dirname, '/public')))
const port = 8080
app.get('/', (req, res) => {
res.render('index')
})
const server = app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
wsServer = new WebSocketServer({
httpServer: server
});
function originIsAllowed(origin) {
return true;
}
wsServer.on('request', function(request) {
if (!originIsAllowed(request.origin)) {
request.reject();
console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
return;
}
var connection = request.accept(null, request.origin);
console.log((new Date()) + ' Connection accepted.');
connection.on('message', function(message) {
if (message.type === 'utf8') {
console.log('Received Message: ' + message.utf8Data);
connection.sendUTF("server says hi");
}
else if (message.type === 'binary') {
console.log('Received Binary Message of ' + message.binaryData.length + ' bytes');
connection.sendBytes(message.binaryData);
}
});
connection.on('close', function(reasonCode, description) {
console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
});
});
client.js:
const socket = new WebSocket('ws://localhost:8080');
// Connection opened
socket.addEventListener('open', function (event) {
socket.send('Hello to Server!');
});
// Listen for messages
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
});
I am not sure if I understand the question, but if you want the client to send message to server, you have do it the same way as it is done in open listener:
socket.send('MESSAGE FOR SERVER') or, if server should send something to client, then just
connection.send('MESSAGE FOR CLIENT').
Realtime communication with WebSocket means, the connection is created only once and the protocol is kept opened (Unlike REST API, where the connection is created with every request.)
The message must be sent actively either from server or client, there is nothing observing some message state and updating it on the other side.

How to make an HTTP/HTTPS request within a Node.js server?

I'm just starting to learn Node.js and right now, I'm trying to use a Node.js Application through cPanel to provide a JSON response when its app's URL is accessed.
When visiting the app's URL, it's evident that the Node.js server is working as it should. So after editing the main JS file, and restarting the Node.js app, changes are reflected when visiting the URL again.
My problem:
Within the function of https.createServer( function (req, res) {});, I want to make an HTTPS request to a PHP file elsewhere, which returns a JSON response. At the moment, I can't even get a response or error from any type of request with the PHP file.
var https = require('https');
var server = https.createServer(function (req, res) {
var message = "";
res.writeHead(200, {
'Content-Type': 'text/plain'
});
var options = {
host: "mydomain.com",
path: '/myPhpScript.php'
};
https.get(options, function(res) {
var bodyChunks = [];
res.on('data', function(chunk) {
bodyChunks.push(chunk);
}).on('end', function() {
var body = Buffer.concat(bodyChunks);
message += body;
})
}).on('error', function(e) {
message += e;
});
res.end(message);
});
server.listen();
As you can see, message would be what's displayed to the browser window, but it's empty. Nothing appears when visiting the App URL. Is it possible to make an HTTPS request with a Node.js HTTPS server?
Note:
I've also tried with native-request and axios and have experienced the same issue.
Server code:
var http = require('http');
var https = require("https");
var server = http.createServer(function (req, res) {
let call = new Promise((resolve, reject) => {
var options = {
host: "jarrenmorris.com",
port: 443,
path: '/gamesense/r6_db/1.json'
};
https.get(options, function (res) {
var bodyChunks = [];
res.on('data', function (chunk) {
bodyChunks.push(chunk);
}).on('end', function () {
resolve(Buffer.concat(bodyChunks));
});
}).on('error', function (e) {
reject(e);
});
});
call.then((data) => {
// do something here with the successful request/json
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end(data);
}).catch((err) => {
// do something here with the failure request/json
// res.write("ERROR:");
res.end(err);
});
});
server.listen(8081, "127.0.0.1", () => {
console.log(`Server listen on ${server.address().address}:${server.address().port} `);
});
Response:
{"name":"tim","age":"42"}
First thing i noticed, while i tried to run your code was, you cant establish a connection to your node.js.
The reason for this was you use the https module, but didnt specify an certificates/keyfiles. Skip this, and work with http till you get the result you want.
Then i wrapped you https request to the external api/file in a promise.
This allows a simple chaining and better readability of the code.
When the promises resolves/fullfill, we answer the request on the http server with the data we received from the external request.
The res.end in your code (where you put it) made no sense, since you didnt wait for the external request to complete. Thats the reason why its nothing is shown in the browser window.

Using Server Sent Events with express

I'll try to make this as simple as possible so i'm not having to post a ton of code. Heres what my app does right now:
User uploads an audio file from the browser
That file is processed on my server, this process takes some time and has about 8 or so steps to complete.
Once everything is finished, the user gets feedback in the browser that the process is complete.
What I want to add to this, is after every step in the process that is completed, send some data back to the server. For example: "Your file is uploaded", "Meta data processed", "image extracted" etc etc so the user gets incremental feedback about what is happening and I believe Server Sent Events can help me do this.
Currently, the file is POSTed to the server with app.post('/api/track', upload.single('track'), audio.process). audio.process is where all the magic happens and sends the data back to the browser with res.send(). Pretty typical.
While trying to get the events working, I have implemented this function
app.get('/stream', function(req, res) {
res.sseSetup()
for (var i = 0; i < 5; i++) {
res.sseSend({count: i})
}
})
and when the user uploads a file from the server I just make a call to this route and register all the necessary events with this function on the client side:
progress : () => {
if (!!window.EventSource) {
const source = new EventSource('/stream')
source.addEventListener('message', function(e) {
let data = JSON.parse(e.data)
console.log(e);
}, false)
source.addEventListener('open', function(e) {
console.log("Connected to /stream");
}, false)
source.addEventListener('error', function(e) {
if (e.target.readyState == EventSource.CLOSED) {
console.log("Disconnected from /stream");
} else if (e.target.readyState == EventSource.CONNECTING) {
console.log('Connecting to /stream');
}
}, false)
} else {
console.log("Your browser doesn't support SSE")
}
}
this works as expected, when I upload a track, i get a stream of events counting from 0-4. So thats great!
My Problem/Question: How do i send relevant messages from the audio.process route, to the /stream route so that the messages can be related to whats happening. audio.process has to be a POST, and /stream has to be a GET with the header 'Content-Type': 'text/event-stream'. It seems kind of weird to make GET requests from within audio.process but is this the best way?
Any and all advice/tips are appreciated! Let me know if you need any more info.
New Answer:
Just use socket.io, it's so much easier and better!
https://www.npmjs.com/package/socket.io#in-conjunction-with-express
basic setup:
const express = require('express');
const PORT = process.env.PORT || 5000;
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
// listen to socket connections
io.on('connection', function(socket){
// get that socket and listen to events
socket.on('chat message', function(msg){
// emit data from the server
io.emit('chat message', msg);
});
});
// Tip: add the `io` reference to the request object through a middleware like so:
app.use(function(request, response, next){
request.io = io;
next();
});
server.listen(PORT);
console.log(`Listening on port ${PORT}...`);
and in any route handler, you can use socket.io:
app.post('/post/:post_id/like/:user_id', function likePost(request, response) {
//...
request.io.emit('action', 'user liked your post');
})
client side:
<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
$(function () {
var socket = io();
$('form').submit(function(e){
e.preventDefault(); // prevents page reloading
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
});
});
</script>
full example: https://socket.io/get-started/chat/
Original Answer
Someone (user: https://stackoverflow.com/users/451634/benny-neugebauer | from this article: addEventListener on custom object) literally gave me a hint on how to implement this without any other package except express! I have it working!
First, import Node's EventEmitter:
const EventEmitter = require('events');
Then create an instance:
const Stream = new EventEmitter();
Then create a GET route for event streaming:
app.get('/stream', function(request, response){
response.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
Stream.on("push", function(event, data) {
response.write("event: " + String(event) + "\n" + "data: " + JSON.stringify(data) + "\n\n");
});
});
In this GET route, you are writing back that the request is 200 OK, content-type is text/event-stream, no cache, and to keep-alive.
You are also going to call the .on method of your EventEmitter instance, which takes 2 parameters: a string of the event to listen for and a function to handle that event(that function can take as much params as it is given)
Now.... all you have to do to send a server event is to call the .emit method of your EventEmitter instance:
Stream.emit("push", "test", { msg: "admit one" });
The first parameter is a string of the event you want to trigger (make sure that it is the same as the one in the GET route). Every subsequent parameter to the .emit method will be passed to the listener's callback!
That is it!
Since your instance was defined in a scope above your route definitions, you can call the .emit method from any other route:
app.get('/', function(request, response){
Stream.emit("push", "test", { msg: "admit one" });
response.render("welcome.html", {});
});
Thanks to how JavaScript scoping works, you can even pass that EventEmitter instance around to other function, even from other modules:
const someModule = require('./someModule');
app.get('/', function(request, response){
someModule.someMethod(request, Stream)
.then(obj => { return response.json({}) });
});
In someModule:
function someMethod(request, Stream) {
return new Promise((resolve, reject) => {
Stream.emit("push", "test", { data: 'some data' });
return resolve();
})
}
That easy! No other package needed!
Here is a link to Node's EventEmitter Class: https://nodejs.org/api/events.html#events_class_eventemitter
My example:
const EventEmitter = require('events');
const express = require('express');
const app = express();
const Stream = new EventEmitter(); // my event emitter instance
app.get('/stream', function(request, response){
response.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
Stream.on("push", function(event, data) {
response.write("event: " + String(event) + "\n" + "data: " + JSON.stringify(data) + "\n\n");
});
});
setInterval(function(){
Stream.emit("push", "test", { msg: "admit one" });
}, 10000)
UPDATE:
i created a module/file that is easier to use and doesn't cause memory leaks!
const Stream = function() {
var self = this;
// object literal of connections; IP addresses as the key
self.connections = {};
self.enable = function() {
return function(req, res, next) {
res.sseSetup = function() {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
}
res.sseSend = function(id, event, data) {
var stream = "id: " + String(id) + "\n" +
"event: " + String(event) + "\n" +
"data: " + JSON.stringify(data) +
"\n\n";
// console.log(id, event, data, stream);
res.write(stream);
}
next()
}
}
self.add = function(request, response) {
response.sseSetup();
var ip = String(request.ip);
self.connections[ip] = response;
}.bind(self);
self.push_sse = function(id, type, obj) {
Object.keys(self.connections).forEach(function(key){
self.connections[key].sseSend(id, type, obj);
});
}.bind(self);
}
/*
Usage:
---
const express = require('express');
const Stream = require('./express-eventstream');
const app = express();
const stream = new Stream();
app.use(stream.enable());
app.get('/stream', function(request, response) {
stream.add(request, response);
stream.push_sse(1, "opened", { msg: 'connection opened!' });
});
app.get('/test_route', function(request, response){
stream.push_sse(2, "new_event", { event: true });
return response.json({ msg: 'admit one' });
});
*/
module.exports = Stream;
Script located here - https://github.com/ryanwaite28/script-store/blob/master/js/express-eventstream.js

How to use server-sent-events in express.js

I setup my REST server with express.js. Now I want to add sse to this server. After I implemented this sse package, I get an error. I know that I get this error, when would try to use res.send twice, but I am not.
ERROR: Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (http.js:690:11)
at ServerResponse.header (/home/root/node_modules/express/lib/response.js:718:10)
at ServerResponse.send (/home/root/node_modules/express/lib/response.js:163:12)
at app.get.str (/home/root/.node_app_slot/main.js:1330:25)
at Layer.handle [as handle_request] (/home/root/node_modules/express/lib/router/layer.js:95:5)
at next (/home/root/node_modules/express/lib/router/route.js:131:13)
at sse (/home/root/node_modules/server-sent-events/index.js:35:2)
at Layer.handle [as handle_request] (/home/root/node_modules/express/lib/router/layer.js:95:5)
at next (/home/root/node_modules/express/lib/router/route.js:131:13)
at Route.dispatch (/home/root/node_modules/express/lib/router/route.js:112:3)
Is it possible that I can't use the express methods anymore within the sse function? For example:
app.get('/events', sse, function(req, res) {
res.send('...');
});
Furthermore, I found this solution and this. Is it possible to make sse with the res.write function or in another way without using another package?
I disagree with using Socket.IO to implement basic Server-Sent Events. The browser API is dead simple and the implementation in Express requires only a couple of changes from a normal response route:
app.get('/streaming', (req, res) => {
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders(); // flush the headers to establish SSE with client
let counter = 0;
let interValID = setInterval(() => {
counter++;
if (counter >= 10) {
clearInterval(interValID);
res.end(); // terminates SSE session
return;
}
res.write(`data: ${JSON.stringify({num: counter})}\n\n`); // res.write() instead of res.send()
}, 1000);
// If client closes connection, stop sending events
res.on('close', () => {
console.log('client dropped me');
clearInterval(interValID);
res.end();
});
});
Set the appropriate headers as per the spec
Use res.flushHeaders() to establish SSE connection
Use res.write() instead of res.send() to send data
To end stream from the server, use res.end()
The snippet above uses setInterval() to simulate sending data to the client for 10 seconds, then it ends the connection. The client will receive an error for the lost connection and automatically try to re-establish the connection. To avoid this, you can close the client on error, or have the browser send a specific event message that the client understands means to close gracefully. If the client closes the connection, we can catch the 'close' event to gracefully end the connection on the server and stop sending events.
express: 4.17.1
node: 10.16.3
You can definitely achieve this without other packages.
I wrote a blog post about this, part 1 sets out the basics.
You mustn't close the SSE as that breaks the functionality. The whole point is that it is an open HTTP connection. This allows for new events to be pushed to the client at any point.
This adds a complete, runnable example (with client to read the stream) to John's excellent answer and makes a tweak, adding the Connection: keep-alive header.
server.js:
const express = require("express");
const fs = require("fs").promises;
const path = require("path");
const app = express();
app
.set("port", process.env.PORT || 5000)
.get("/", (req, res) => {
fs.readFile(path.join(__dirname, "client.html"))
.then(file => res.send(file.toString()))
.catch(err => res.status(404).send(err.message))
;
})
.get("/stream", (req, res) => {
res.set({
"Access-Control-Allow-Origin": "*",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "text/event-stream",
});
res.flushHeaders();
let counter = 0;
const interval = setInterval(() => {
res.write("" + counter++);
}, 1000);
res.on("close", () => {
clearInterval(interval);
res.end();
});
})
.listen(app.get("port"), () =>
console.log(`server listening on port ${app.get("port")}`)
)
;
client.html:
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<script>
(async () => {
const response = await fetch("/stream");
if (!response.ok) {
throw Error(response.status);
}
for (const reader = response.body.getReader();;) {
const {value, done} = await reader.read();
if (done) {
break;
}
document.body.innerText = new TextDecoder().decode(value);
}
})();
</script>
</body>
</html>
After node server.js, navigate your browser to localhost:5000. You can also test the stream directly with curl localhost:5000/stream.
I won't repeat the notes from John's answer, but, in short we set the necessary headers and flush them to begin the connection, then use res.write to send a chunk of data. Call res.end() to terminate the connection on the server or listen for res.on("close", ...) for the client closing the connection.
The client uses fetch and response.body.getReader() which can be read with const {value, done} = await reader.read() and decoded with TextDecoder().decode(value).
See also https://masteringjs.io/tutorials/express/server-sent-events
Express 4.17.1, Node 15.2.0, Chrome 89.0.4389.128 (Official Build) (64-bit)
It appears from the documentation on the library you're using that you should use a res.sse when using that as middleware on a function. See:
https://www.npmjs.com/package/server-sent-events
But, all this is actually doing from their code is wrapping res.write as you mentioned. See:
https://github.com/zacbarton/node-server-sent-events/blob/master/index.js#L11
Self-promotion: I wrote the ExpreSSE package that provides middlewares for working with SSE in express, you can find it on npm: #toverux/expresse.
A simple example:
router.get('/events', sse(/* options */), (req, res) => {
let messageId = parseInt(req.header('Last-Event-ID'), 10) || 0;
someModule.on('someEvent', (event) => {
//=> Data messages (no event name, but defaults to 'message' in the browser).
res.sse.data(event);
//=> Named event + data (data is mandatory)
res.sse.event('someEvent', event);
//=> Comment, not interpreted by EventSource on the browser - useful for debugging/self-documenting purposes.
res.sse.comment('debug: someModule emitted someEvent!');
//=> In data() and event() you can also pass an ID - useful for replay with Last-Event-ID header.
res.sse.data(event, (messageId++).toString());
});
});
There is also another middleware to push the same events to multiple clients.
New Answer:
Just use socket.io, it's so much easier and better!
https://www.npmjs.com/package/socket.io#in-conjunction-with-express
basic setup:
const express = require('express');
const PORT = process.env.PORT || 5000;
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
// listen to socket connections
io.on('connection', function(socket){
// get that socket and listen to events
socket.on('chat message', function(msg){
// emit data from the server
io.emit('chat message', msg);
});
});
// Tip: add the `io` reference to the request object through a middleware like so:
app.use(function(request, response, next){
request.io = io;
next();
});
server.listen(PORT);
console.log(`Listening on port ${PORT}...`);
and in any route handler, you can use socket.io:
app.post('/post/:post_id/like/:user_id', function likePost(request, response) {
//...
request.io.emit('action', 'user liked your post');
})
client side:
<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
$(function () {
var socket = io();
$('form').submit(function(e){
e.preventDefault(); // prevents page reloading
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
});
});
</script>
full example: https://socket.io/get-started/chat/
Original Answer:
Someone (user: https://stackoverflow.com/users/451634/benny-neugebauer | from this article: addEventListener on custom object) literally gave me a hint on how to implement this without any other package except express! I have it working!
First, import Node's EventEmitter:
const EventEmitter = require('events');
Then create an instance:
const Stream = new EventEmitter();
Then create a GET route for event streaming:
app.get('/stream', function(request, response){
response.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
Stream.on("push", function(event, data) {
response.write("event: " + String(event) + "\n" + "data: " + JSON.stringify(data) + "\n\n");
});
});
In this GET route, you are writing back that the request is 200 OK, content-type is text/event-stream, no cache, and to keep-alive.
You are also going to call the .on method of your EventEmitter instance, which takes 2 parameters: a string of the event to listen for and a function to handle that event(that function can take as much params as it is given)
Now.... all you have to do to send a server event is to call the .emit method of your EventEmitter instance:
Stream.emit("push", "test", { msg: "admit one" });
The first parameter is a string of the event you want to trigger (make sure that it is the same as the one in the GET route). Every subsequent parameter to the .emit method will be passed to the listener's callback!
That is it!
Since your instance was defined in a scope above your route definitions, you can call the .emit method from any other route:
app.get('/', function(request, response){
Stream.emit("push", "test", { msg: "admit one" });
response.render("welcome.html", {});
});
Thanks to how JavaScript scoping works, you can even pass that EventEmitter instance around to other function, even from other modules:
const someModule = require('./someModule');
app.get('/', function(request, response){
someModule.someMethod(request, Stream)
.then(obj => { return response.json({}) });
});
In someModule:
function someMethod(request, Stream) {
return new Promise((resolve, reject) => {
Stream.emit("push", "test", { data: 'some data' });
return resolve();
})
}
That easy! No other package needed!
Here is a link to Node's EventEmitter Class: https://nodejs.org/api/events.html#events_class_eventemitter
My example:
const EventEmitter = require('events');
const express = require('express');
const app = express();
const Stream = new EventEmitter(); // my event emitter instance
app.get('/stream', function(request, response){
response.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
Stream.on("push", function(event, data) {
response.write("event: " + String(event) + "\n" + "data: " + JSON.stringify(data) + "\n\n");
});
});
setInterval(function(){
Stream.emit("push", "test", { msg: "admit one" });
}, 10000)

Why I couldn't get first few responses from nodeJS' httpServer?

I'm planning to use nodeJS as my comet server and I wrote some code for testing, but there is an issue that when client connected to the server for the first time, it couldn't get response from server.
Here is the server-side code (server.js):
var util = require('util');
var redis = require('redis').createClient(6379, '192.168.1.254');
var http = require('http');
redis.on("error", function (err) {
console.log("Error " + err);
});
var server = http.createServer(requestListener);
server.listen(9898, '192.168.1.254');
function requestListener(req, res) {
util.log('Connected.');
redis.brpoplpush('msg:q:1', 'msg:s:1', 20, function(err, reply) {
if (err) {
util.log('ERROR: ' + err);
}
var length = reply ? reply.length : 0;
res.writeHead(200, {
'Content-Type':'text/plain',
'Content-Length':length
});
if (length) {
res.end(reply);
util.log('Sent: ' + reply);
} else {
res.end('');
}
});
}
And the client code (client.sh):
#!/bin/bash
while [ 1 ]
do
curl -i http://192.168.1.254:9898
done
I tested it following steps:
node server.js
./client.sh
In redis, LPUSH('msg:q:1', 'blablah')
Now, "Sent: blablah" printed on console, res.end(reply) excuted, but client receives nothing. I repeat step 3 for many times, then it works as expect. If I restart the client, the first few responses can't be received again.
How can I resolve this?
I think what might happening here is you've aborted curl while it was waiting for the response from redis. After the HTTP client is aborted, the redis command still stays active. You then push another element onto the queue, the redis command returns, but has no HTTP response to write it to. When you start the curl loop again, you find the queue empty.
Here's a modified version of your program that streams the response and detects a client abort. It doesn't put the element back on the queue, but you could certainly do that as well.
var util = require('util');
var redis = require('redis').createClient();
var http = require('http');
redis.on("error", function (err) {
console.log("Redis error " + err);
});
redis.on("ready", function () {
console.log("Redis client ready");
});
var server = http.createServer(requestListener);
server.listen(9898);
function requestListener(req, res) {
var aborted = false;
res.writeHead(200, {
"Content-Type": "text/plain"
});
res.write("Checking redis...\n");
redis.brpoplpush('q', 's', 20, function (err, reply) {
if (aborted) {
return console.log("Client aborted before redis reply came back.");
}
if (err) {
return res.end("Redis error");
}
res.end("Redis reply: " + reply);
});
req.on("aborted", function () {
console.log("HTTP client aborted.");
aborted = true;
});
}

Categories

Resources