Streaming data in node.js with ws and websocket-stream - node.js

I'm trying to stream data from a server to a client using websockets -- in particular, ws and websocket stream. However, the stream seems to be closing too early, and I only receive two chunks worth of data. Here is the simple example.
I have a server:
var WebSocketServer = require('ws').Server;
var websocketStream = require('websocket-stream');
var wss = new WebSocketServer({port: 8098});
var fs = require('fs');
var util = require('util');
wss.on('connection', function connect(ws) {
var stream = websocketStream(ws);
var rs = fs.createReadStream('foo.big');
rs.pipe(stream);
});
and a client that connects and streams foo.big to a local file using pipe():
var fs = require('fs');
var util = require('util');
var websocket = require('websocket-stream');
var rs = fs.createWriteStream('big.out');
rs.on('open', function () {
var ws = websocket('http://localhost:8098');
ws.on('open', function () {
ws.pipe(rs);
}).on('error', function (err) {
console.log(err);
}).on('close', function (err) {
console.log("Closing");
});
});
Any ideas why I would only be getting a few packets of data? If I remove the call in the client to pipe, and just receive the data in chunks, it seems to receive all the data.

There is a couple of things wrong with the above (as of 2017).
You would rather want to listen for the fs.readStream() close event
You should not be listening to the websocket-stream open event, as you can assume it to be open
Also, performance: ws is likely not suited for server to server communication. Consider alternatives, like net, like here, or various other lower than ws level ways.
Find a full example here on my GH.
const fs = require('fs')
const websocket = require('websocket-stream')
const writeStream = fs.createWriteStream('big.out')
writeStream.on('open', () => {
let ws = websocket('http://localhost:8098')
ws.pipe(writeStream)
ws.on('error', (err) => {
if (err) throw err
}).on('close', (err) => {
if (err) throw err
console.log(`Closing ws with: ${fs.statSync('big.out').size} bytes`)
})
writeStream.on('close', () => {
console.log(`Closing file stream with: ${fs.statSync('big.out').size} bytes`)
})
})

Related

Node Stream to emulate duplex socket

I'm using node with the mqtt-connection and aedes mqtt libraries. I am using aedes to run a mqtt server, and I wish to connect to the mqtt server from within node using a stream, rather than open a tcp socket. Both libraries will accept a Duplex stream.
Why does something like this work:
const mqttCon = require('mqtt-connection');
const duplex = require('net').createConnection(1883);
const client = mqttCon(duplex, {
protocolVersion: 3
});
While something like this fails (closes the stream)after the first data exchange?
const mqttCon = require('mqtt-connection');
const duplex = new stream.Transform();
duplex._transform = (chunk, encoding, callback) => {
duplex.push(chunk);
console.log(chunk);
callback();
};
const client = mqttCon(duplex, {
protocolVersion: 3
});
aedes.handle(duplex);
I feel like I must have some fundamental misconception about how streams are supposed to work. Basically I want to create something that acts like a TCP socket, allowing these two "processes" to communicate internal to node.
Typical use of aedes to create a mqtt server would looking this this:
const aedes = require('aedes')(
{
concurrency: 500,
maxClientsIdLength: 100
}
);
const server = require('net').createServer(aedes.handle);
Edit: More details about the failure.
Then client feeds data to the stream, then as soon as aedes finishes responding, duplex.on('close') fires. There are no error messages, and no indication from either side that there has been an error, the stream just closes so each side then closes gracefully. I'm guessing that one side or the other sees an "end" to the stream so it closes.
The problem is that "duplex" is a single stream, where it really needs two streams bridged. Here is what works:
const duplexAedes = new stream.Duplex({
write: function (chunk, encoding, next) {
setImmediate(function () {
duplexClient.push(chunk);
});
next();
},
read: function (size) {
// Placeholder
}
});
const duplexClient = new stream.Duplex({
write: function (chunk, encoding, next) {
setImmediate(function () {
duplexAedes.push(chunk);
});
next();
},
read: function (size) {
// Placeholder
}
});
duplexAedes.authentication = clientId;
const client = mqttCon(duplexClient, {
protocolVersion: 3
});
aedes_handle(duplexAedes);

copy an image in nodeJs with fs

Im trying to copy an png file from a folder to another folder in my nodeJs project.i have some problem. new Image file has some problem and cant open.
i use this code
const fs = require('fs');
var inStr = fs.createReadStream(mainDir+"/"+req.body.ExRequestId+".png");
var outStr = fs.createWriteStream(mainDir+"/"+docReq._id + ".png");
inStr.pipe(outStr);
When working with streams, it's good practice to wait for streams to become ready before using them and handle the errors.
The following snippet waits for the ready event on both streams before piping the ReadStream to the WriteStream and handles the errors.
// assuming you're using express and the app instance is bound to a variable named app
const fs = require('fs');
const path = require('path');
// ...
// helper function: returns a promise that gets resolved when the specified event is fired
const waitForEvent = (emitter, event) => new Promise((resolve, reject) => emitter.once(event, resolve));
app.post('/handle-post', async (req, res) => {
// TODO: validate the input
const { ExRequestId } = req.body;
const srcFileName = `${path.join(mainDir, ExRequestId)}.png`;
const destFileName = `${path.join(mainDir, docReq._id)}.png`;
const srcStream = fs.createReadStream(srcFileName);
await waitForEvent(srcStream, "ready");
const destStream = fs.createWriteStream(destFileName);
await waitForEvent(destStream, "ready");
const handleError = err => res.status(500).json(err);
srcStream.on("error", handleError);
destStream.on("error", handleError);
srcStream.pipe(destStream);
await waitForEvent(srcStream, 'end');
res.status(200).json({srcFileName, destFileName});
});
I also put together a minimal working example. It can be found here.
try with this code:
fs.readFile(sourcePath , function (err, data) {
if (err) throw err;
fs.writeFile(destinationPath , data , 'base64' , function (err) {
if (err) throw err;
console.log('It\'s saved!');
});
});

How to disconnect a socket after streaming data?

I am making use of "socket.io-client" and "socket.io stream" to make a request and then stream some data. I have the following code that handles this logic
Client Server Logic
router.get('/writeData', function(req, res) {
var io = req.app.get('socketio');
var nameNodeSocket = io.connect(NAMENODE_ADDRESS, { reconnect: true });
var nameNodeData = {};
async.waterfall([
checkForDataNodes,
readFileFromS3
], function(err, result) {
if (err !== null) {
res.json(err);
}else{
res.json("Finished Writing to DN's");
}
});
function checkForDataNodes(cb) {
nameNodeSocket.on('nameNodeData', function(data) {
nameNodeData = data;
console.log(nameNodeData);
cb(null, nameNodeData);
});
if (nameNodeData.numDataNodes === 0) {
cb("No datanodes found");
}
}
function readFileFromS3(nameNodeData, cb) {
for (var i in nameNodeData['blockToDataNodes']) {
var IP = nameNodeData['blockToDataNodes'][i]['ipValue'];
var dataNodeSocket = io.connect('http://'+ IP +":5000");
var ss = require("socket.io-stream");
var stream = ss.createStream();
var byteStartRange = nameNodeData['blockToDataNodes'][i]['byteStart'];
var byteStopRange = nameNodeData['blockToDataNodes'][i]['byteStop'];
paramsWithRange['Range'] = "bytes=" + byteStartRange.toString() + "-" + byteStopRange.toString();
//var file = require('fs').createWriteStream('testFile' + i + '.txt');
var getFileName = nameNodeData['blockToDataNodes'][i]['key'].split('/');
var fileData = {
'mainFile': paramsWithRange['Key'].split('/')[1],
'blockName': getFileName[1]
};
ss(dataNodeSocket).emit('sendData', stream, fileData);
s3.getObject(paramsWithRange).createReadStream().pipe(stream);
//dataNodeSocket.disconnect();
}
cb(null);
}
});
Server Logic (that gets the data)
var dataNodeIO = require('socket.io')(server);
var ss = require("socket.io-stream");
dataNodeIO.on('connection', function(socket) {
console.log("Succesfully connected!");
ss(socket).on('sendData', function(stream, data) {
var IP = data['ipValue'];
var blockName = data['blockName'];
var mainFile = data['mainFile'];
dataNode.makeDir(mainFile);
dataNode.addToReport(mainFile, blockName);
stream.pipe(fs.createWriteStream(mainFile + '/' + blockName));
});
});
How can I properly disconnect the connections in function readFileFromS3. I have noticed using dataNodeSocket.disconnect() at the end does not work as I cannot verify the data was received on the 2nd server. But if I comment it out, I can see the data being streamed to the second server.
My objective is to close the connections in Client Server side
It appears that the main problem with closing the socket is that you weren't waiting for the stream to be done writing before trying to close the socket. So, because the writing is all asynchronous and finishes sometime later, you were trying to close the socket before the data had been written.
Also because you were putting asynchronous operations inside a for loop, you were also running all your operations in parallel which may not be exactly what you want as it makes error handling more difficult and server load more difficult.
Here's the code I would suggest that does the following:
Create a function streamFileFromS3() that streams a single file and returns a promise that will notify when it's done.
Use await in a for loop with that streamFileFromS3() to serialize the operations. You don't have to serialize them, but then you would have to change your error handling to figure out what to do if one errors while the others are already running and you'd have to be more careful about concurrency issues.
Use try/catch to catch any errors from streamFileFromS3().
Add error handling on the stream.
Change all occurrences of data['propertyName'] to data.propertyName. The only time you need to use brackets is if the property name contains a character that is not allowed in a Javascript identifier or if the property name is in a variable. Otherwise, the dot notation is preferred.
Add socket.io connection error handling logic for both socket.io connections.
Set returned status to 500 when there's an error processing the request
So, here's the code for that:
const ss = require("socket.io-stream");
router.get('/writeData', function(req, res) {
const io = req.app.get('socketio');
function streamFileFromS3(ip, data) {
return new Promise((resolve, reject) => {
const dataNodeSocket = io.connect(`http://${ip}:5000`);
dataNodeSocket.on('connect_error', reject);
dataNodeSocket.on('connect_timeout', () {
reject(new Error(`timeout connecting to http://${ip}:5000`));
});
dataNodeSocket.on('connection', () => {
// dataNodeSocket connected now
const stream = ss.createStream().on('error', reject);
paramsWithRange.Range = `bytes=${data.byteStart}-${data.byteStop}`;
const filename = data.key.split('/')[1];
const fileData = {
'mainFile': paramsWithRange.Key.split('/')[1],
'blockName': filename
};
ss(dataNodeSocket).emit('sendData', stream, fileData);
// get S3 data and pipe it to the socket.io stream
s3.getObject(paramsWithRange).createReadStream().on('error', reject).pipe(stream);
stream.on('close', () => {
dataNodeSocket.disconnect();
resolve();
});
});
});
}
function connectError(msg) {
res.status(500).send(`Error connecting to ${NAMENODE_ADDRESS}`);
}
const nameNodeSocket = io.connect(NAMENODE_ADDRESS, { reconnect: true });
nameNodeSocket.on('connect_error', connectError).on('connect_timeout', connectError);
nameNodeSocket.on('nameNodeData', async (nameNodeData) => {
try {
for (let item of nameNodeData.blockToDataNodes) {
await streamFileFromS3(item.ipValue, item);
}
res.json("Finished Writing to DN's");
} catch(e) {
res.status(500).json(e);
}
});
});
Other notes:
I don't know what paramsWithRange is as it is not declared here and when you were doing everything in parallel, it was getting shared among all the connections which is asking for a concurrency issue. In my serialized implementation, it's probably safe to share it, but the way it is now bothers me as it's a concurrency issue waiting to happen.

How to stream data over socket.io to client

I have socket.io sending a basic object from server to client. This bit works fine.
Now want to send a stream from server to client, using event-stream (specifically the results of a block-chain query). I am getting unexpected results in the browser console..
var io = require('socket.io')(server);
var dsteem = require('dsteem')
var es = require('event-stream')
var util = require('util')
var client = new dsteem.Client('https://api.steemit.com')
var stream = client.blockchain.getBlockStream()
/* This sends results to stdout, fine
io.on('connection', function(socket){
stream.pipe(es.map(function(block, callback) {
callback(null, util.inspect(block) + '\n')
})).pipe(process.stdout);
// And this sends a simple object to the client
socket.emit('blockchainOps', {"Foo!":"Doo!"} );
});
*/
// Putting both together sends strange connection data to client
io.on('connection', function(socket){
socket.emit('blockchainOps', function() {
stream.pipe(es.map(function(block, callback) {
callback(null, util.inspect(block) + '\n');
}))
})
});
What I get in the client console appears to be some kind of TCP socket function,
ƒ (){if(!n){n=!0;var r=a(arguments);u("sending ack %j",r),e.packet({type:i.ACK,id:t,data:r})}}
Can anyone help me understand what's going on and what I'm doing wrong?
== EDIT UPDATE ==
As suggested in comments, I've tried socket.io-stream to augment event-stream.
var es = require('event-stream')
var util = require('util')
var ss = require('socket.io-stream');
var stream = ss.createStream();
io.on('connection', function(socket){
ss(socket).emit('blockchainOps', stream, function(){
client.blockchain.getBlockStream()
.pipe(es.map(function(block, callback) {
callback(null, util.inspect(block) + '\n')
}))
.pipe(process.stdout)
}());
});
This time I get a socket object returned in the browser console which does not seem to be the stream data I was hoping for.
If anyone is looking for a working socket.io stream example
// server side
const { pipeline } = require('stream')
const server = require('http').Server().listen(8080)
const io = require('socket.io')(server)
const ss = require('socket.io-stream')
io.on('connection', (socket) => ss(socket).on('stream', (stream) => {
pipeline(stream, process.stdout, (err) => err && console.log(err))
}));
// client side
const client = require('socket.io-client')
const socket = client.connect('http://localhost:8080')
socket.on('connect', () => {
const stream = ss.createStream()
ss(socket).emit('stream', stream)
pipeline(process.stdin, stream, (err) => err && console.log(err))
});
You're using socket.emit wrong, you're passing the ACK callback to the client instead of your stream. Have a look at socket.emit signature :socket.emit(eventName[, ...args][, ack]).
You probably want something like
socket.emit('blockchainOps', client.blockchain.getBlockStream());
However, I don't think plain socket io supports passing a Stream like that. To pipe a stream down to the client you could use socketio-stream. It would look like this:
var ss = require('socket.io-stream');
var stream = ss.createStream();
ss(socket).emit('blockchainOps', stream);
client.blockchain.getBlockStream().pipe(stream);
EDIT:
On the client, you should be able to read your stream like this:
<script src="socket.io/socket.io.js"></script>
<script src="socket.io-stream.js"></script>
...
ss(socket).on('blockchainOps', function(stream) {
var binaryString = "";
stream.on('data', function(data) {
for(var i=0;i<data.length;i++) {
binaryString+=String.fromCharCode(data[i]);
}
});
stream.on('end', function(data) {
console.log(binaryString);
binaryString = "";
});
});

get a response from cassandra binary protocol using node streams

Trying to learn more about streams and cassandra, so figured I'd try out the binary protocol. I can't even get it to respond though.
var net = require('net');
var util = require('util');
var stream = require('stream');
var session;
session = net.connect({ port: 9160 }, function () {
console.log('connected');
var header = new Buffer([ 0x01, 0x00, 0x01, 0x01 ]);
var length = new Buffer(4);
var body = new Buffer("{'CQL_VERSION':'3.0.0'}", 'utf8');
length.writeUInt32BE(body.length, 0);
session.write(header);
session.write(length);
session.write(body);
setTimeout(function () { session.end(); }, 5000);
});
session.on('error', function (err) {
console.log(err);
});
var client = new (stream.Writable);
client._write = function (chunk, _, next) {
console.log('response received');
next();
};
session.pipe(client);
The program runs for 5 seconds, printing "connected" almost immediately, but the writable stream never receives content from the database. Any help would be much appreciated!
I'm working off of this documentation for the binary protocol and this guide to node streams.
Cassandra is running, version 2.0.5.
9160 is the Thrift's port. Have you tried your code with port 9042?

Resources