Return value, or stop script - node.js

Helo,
i create API in Windows Azure Mobile service, In this api script i have function to connect the other service. I have problem how to return value or stop executable my script when i have good answer from service. Function process.exit(1), don't work.
function function1(item,response) {
var buf ='';
var net = require('net');
var HOST = 'xxxx.xxxx.xxxx.xxxx';
var PORT = xxx;
var client = new net.Socket();
client.setTimeout(100000, function() {
console.log("Timeout");
response.send(500, "Timeout");
});
client.connect(PORT, HOST, function() {
client.write(item + "\n");
client.on('data', function(data) {
buf = buf + data.toString('utf-8');
});
client.on('close', function() {
});
client.on('end', function() {
if (buf.length > 1) {
var result = JSON.parse(buf);
//if resulr.Aviable is true the functios should return result or send result and stop execiuting script
if ( result.Avaiable) {
response.send(200, result);
//now i wont't to respond answer to client or return my value(result)
console.log('Send data');
}
}
client.destroy();
});
});
}

One alternative is to have a flag which indicates whether a response has been sent or not. This way, when the first of the alternatives is reached, you can set the flag to true (possibly clearing the timeout so it doesn't linger more than it needs) and in all cases check whether the flag has been set before returning the response. Something along the lines of the code below:
function function1(item,response) {
var buf = '';
var net = require('net');
var HOST = 'xxxx.xxxx.xxxx.xxxx';
var PORT = xxx;
var client = new net.Socket();
var responseSent = false;
var timeoutHandler = client.setTimeout(100000, function() {
if (!responseSent) {
responseSent = true;
console.log("Timeout");
response.send(500, { error: "Timeout" });
}
});
client.connect(PORT, HOST, function() {
client.write(item + "\n");
client.on('data', function(data) {
buf = buf + data.toString('utf-8');
});
client.on('close', function(had_error) {
if (!responseSent) {
clearTimeout(timeoutHandler);
responseSent = true;
console.log('Socket closed');
response.send(500, { error: had_error ? 'Socket error' : 'unknown' });
}
});
client.on('end', function() {
if (!responseSent) {
responseSent = true;
clearTimeout(timeoutHandler);
if (buf.length > 1) {
try {
var result = JSON.parse(buf);
if (result.Available) {
response.send(200, result);
} else {
response.send(500, { error: 'Socket data is not available' });
}
} catch (ex) {
response.send(500, { error: 'error parsing JSON', exception: ex });
}
} else {
// We should always return a response
response.send(500, { error: 'No data read from socket' });
}
}
client.destroy();
});
});
}
Notice that since node.js runs on a single thread, you can assume that there will be no cases where the response is sent twice. Also, you should make sure that the response is always sent once - in the code you had, if there was a socket error, or if buf.length was not greater than 1, or if result.Avaiable was not true, then the timeout response would be sent, but you didn't need to wait for the whole (100 seconds) time to send that response.

Related

Dequeue request in Azure Service Bus when queue length < 10 frequently returns null

I have been experimenting with Azure Service Bus queues in NodeJS. I have built the sender.js and listener.js based on their code sample in the documentation. Building a queue works fine. Dequeuing and deleting messages from the queue works fine until message length reaches 10. At this point, dequeue requests return null messages around 4 out of 5 times. If I keep looping the dequeue requests, eventually, it will dequeue and delete those last 10 messages. But, this seems highly inefficient. Has anyone else experience this problem?
listener.js
var azure = require('azure');
var async = require("async");
var connectionString = process.env.CONNECTION_STRING || "Endpoint=sb://endpoint"; // dev
console.log(process.env.CONNECTION_STRING);
var serviceBusService = azure.createServiceBusService(connectionString);
// var serviceBusService = azure.createServiceBusService();
exports.createQueue = function (req,res) {
var body = req.body;
serviceBusService.createQueueIfNotExists(body.queueName, function(error){
console.log(error);
if(!error){
// Queue exists
return res.send(200);
} else {
return res.send(500, error);
}
});
};
exports.sendMessageToQueue = function (req, res) {
var body = req.body;
var message = {
body: 'Test message',
customProperties: {
testproperty: 'TestValue'
}};
serviceBusService.sendQueueMessage(body.queueName, message, function(error){
if(!error){
// message sent
return res.send(200);
} else {
return res.send(500, error);
}
});
}
exports.receiveMessageFromQueue = function (req, res) {
var body = req.body;
serviceBusService.receiveQueueMessage(body.queueName, function(error, receivedMessage){
if(!error){
console.log(receivedMessage);
// Message received and deleted
return res.send(200,receivedMessage);
} else {
return res.send(500, error);
}
});
}
function _receiveMessageFromQueue(queueName,delayTimeIfQueueIsEmpty,callback) {
serviceBusService.receiveQueueMessage(queueName, function(error, receivedMessage){
console.log(error, receivedMessage);
// console.log(error);
if (error == 'No messages to receive') {
// call the rest of the code and have it execute after 30 seconds
setTimeout(function() {
callback(receivedMessage);
}, delayTimeIfQueueIsEmpty);
} else {
// callback immediately
callback(receivedMessage);
}
});
}
function _sendQueueMessage(queueName,message,callback) {
serviceBusService.sendQueueMessage(queueName, message, function(error){
console.log(error);
callback();
});
}
function listenMessageQueue(concurrency,delayTimeIfQueueIsEmpty,queueName) {
var taskHandler = function(task, done) {
_receiveMessageFromQueue(task.queueName, delayTimeIfQueueIsEmpty, function(message) {
if (message) {
console.log('hello ' + message.body);
}
myQueue.push({ id: task.id + 1, queueName: queueName, url: "http://localhost/get-person/" + task.id + 1});
done();
});
};
var queueSize = concurrency;
var myQueue = async.queue(taskHandler, queueSize);
myQueue.drain = function() {
console.log("All the work has been done.");
}
for(var i = 0; i < concurrency; i++) {
myQueue.push({ id: i, queueName: queueName, url: "http://localhost/get-person/"+i });
}
}
delayTimeIfQueueIsEmpty = 30000; // 30s
concurrency = 2;
queueName = "jobs";
// listen and dequeue message from azure message bus
listenMessageQueue(concurrency,delayTimeIfQueueIsEmpty,queueName);
sender.js
var azure = require('azure');
var async = require("async");
var connectionString = process.env.CONNECTION_STRING || "Endpoint=sb://endpoint";
console.log(process.env.CONNECTION_STRING);
var serviceBusService = azure.createServiceBusService(connectionString);
exports.createQueue = function (req,res) {
var body = req.body;
serviceBusService.createQueueIfNotExists(body.queueName, function(error){
console.log(error);
if(!error){
// Queue exists
return res.send(200);
} else {
return res.send(500, error);
}
});
};
exports.sendMessageToQueue = function (req, res) {
var body = req.body;
var message = {
body: 'Test message',
customProperties: {
testproperty: 'TestValue'
}};
serviceBusService.sendQueueMessage(body.queueName, message, function(error){
if(!error){
// message sent
return res.send(200);
} else {
return res.send(500, error);
}
});
}
exports.receiveMessageFromQueue = function (req, res) {
var body = req.body;
serviceBusService.receiveQueueMessage(body.queueName, function(error, receivedMessage){
if(!error){
console.log(receivedMessage);
// Message received and deleted
return res.send(200,receivedMessage);
} else {
return res.send(500, error);
}
});
}
function _receiveMessageFromQueue(queueName,delayTimeIfQueueIsEmpty,callback) {
serviceBusService.receiveQueueMessage(queueName, function(error, receivedMessage){
console.log(error, receivedMessage);
// console.log(error);
if (error == 'No messages to receive') {
// call the rest of the code and have it execute after 30 seconds
setTimeout(function() {
callback(receivedMessage);
}, delayTimeIfQueueIsEmpty);
} else {
// callback immediately
callback(receivedMessage);
}
});
}
function _sendQueueMessage(queueName,message,callback) {
serviceBusService.sendQueueMessage(queueName, message, function(error){
console.log(error);
callback();
});
}
function listenMessageQueue(concurrency,delayTimeIfQueueIsEmpty,queueName) {
var taskHandler = function(task, done) {
_receiveMessageFromQueue(task.queueName, delayTimeIfQueueIsEmpty, function(message) {
if (message) {
console.log('hello ' + message.body);
}
myQueue.push({ id: task.id + 1, queueName: queueName, url: "http://localhost/get-person/" + task.id + 1});
done();
});
};
var queueSize = concurrency;
var myQueue = async.queue(taskHandler, queueSize);
myQueue.drain = function() {
console.log("All the work has been done.");
}
for(var i = 0; i < concurrency; i++) {
myQueue.push({ id: i, queueName: queueName, url: "http://localhost/get-person/"+i });
}
}
function pushMessageQueue(concurrency,queueName) {
var taskHandler = function(task, done) {
var message = {
body: String(task.id),
customProperties: {
url: task.url
}};
_sendQueueMessage(task.queueName, message, function() {
console.log('hello ' + task.id);
myQueue.push({ id: task.id + 1, queueName: queueName, url: "http://localhost/get-person/" + task.id + 1});
done();
});
};
var queueSize = concurrency;
var myQueue = async.queue(taskHandler, queueSize);
myQueue.drain = function() {
console.log("All the work has been done.");
}
for(var i = 0; i < concurrency; i++) {
myQueue.push({ id: i, queueName: queueName, url: "http://localhost/get-person/"+i });
}
}
concurrency = 2;
queueName = "jobs";
pushMessageQueue(concurrency,queueName); // push message to queue for testing: 100 messages per call
finally was able to get through to Azure support and found the answer. ServiceBus by default enables partitioning. When making http requests (the NodeJS SDK for Azure ServiceBus makes http REST calls), when message count is low can result in a partitions with different sets of messages, as they have not had a chance to sync. This is resolved by creating a new Queue that disables partitioning or by increasing the keep alive or by using the DotNet SDK which allows https requests to be made.

nodejs-serialport => RE-Establish connection to port after closed

Accordding to my last question SerialPort 'close' event never fire. I was unabled to detected if the COM is disconnected so I have created my own way to detect it.
I have created timestamp, and checked it with interval() every 1 sec to see if it is connected.
when it's detect the COM is unplugged I have try to re-establish the connection or re-instance port with SerialPort like you'll see inside the code below.
When it's try to reconnect I've get Error: Access denied.
There is a way to refresh or clean the cache? , because I think the server still hold the connection when isn't closed propely.
I've also tried port.close() and it's throw me out: Error: Port is not open.
var comPort = '\\\\.\\COM7',
lastDataTime,
lastresult,
count = 0,
lastDataTime,
comStatus,
error;
var port = new SerialPort(comPort, function (err) {
if (err) {
comStatus = false;
return console.log('Error: ', err.message);
}
});
const parser = port.pipe(new Readline());
port.on('open', function () {
console.log('~Port is open.');
parser.on('data', function (data) {
comStatus = true;
lastDataTime = Date.now();
if (++count == 10) {
count = 0;
lastresult = data;
}
});
});
setInterval(function () {
if (Date.now() - lastDataTime > 1000 || !comStatus) {
comStatus = false;
port.close();
port = new SerialPort(comPort, function (err) {
if (err) {
error = 'Error: ' + err.message;
return console.log(error);
}
});
}
}, 1000);
app.get('/', function (req, res) {
res.send((comStatus) ? lastresult : 'Disconnected - ' + error);
console.log(lastresult);
})
Thanks!
As you can see in /node_modules/serialport/lib/serialport.js: close-event may not be emitted (unlike disconnect).
You can add console.log locally like below to simple debug.
P.S. I tested it on Win7x32. Close-event is emitted.
SerialPort.prototype._disconnected = function(err) {
this.paused = true;
this.emit('disconnect', err);
// add: console.log('1', this.closing);
if (this.closing) {
return;
}
// add: console.log('2', this.fd);
if (this.fd === null) {
return;
}
this.closing = true;
if (process.platform !== 'win32') {
this.readable = false;
this.serialPoller.close();
}
// add: console.log('3');
SerialPortBinding.close(this.fd, function(err) {
// add: console.log('4', this._events.close.toString());
this.closing = false;
if (err) {
debug('Disconnect close completed with error: ', err);
}
this.fd = null;
this.emit('close'); // it's your target
}.bind(this));
};
Reconnect example
var SerialPort = require('serialport');
var port = new SerialPort('COM1', {autoOpen: false, baudRate: 9600});
function open () {
port.open(functon (err) {
if (!err)
return;
console.log('Port is not open: ' + err.message);
setTimeout(open, 10000); // next attempt to open after 10s
});
}
port.on('open', function() {
function send() {
if (!port.isOpen()) // v5.x require
return console.log('Port closed. Data is not sent.');
port.write(123, function (err) {
if (err)
console.log('Error on write: ' + err.message)
port.drain(() => console.log('DONE'));
});
}
setInterval(send, 1000);
});
port.on('close', function () {
console.log('CLOSE');
open(); // reopen
});
port.on('data', (data) => console.log('Data: ' + data));
port.on('error', (err) => console.error('Error: ', err.message));
open(); // open manually
According to the serialport.io,
The resume() method causes an explicitly paused, Readable stream to
resume emitting 'data' events, switching the stream into flowing mode.
Simply, when port is closes, serialport library emits a close event
serialport.on('close', function(error){
if(error.disconnected === true){
console.log("disconnected");
}
}
, which will allow us whether port is disconnected or not.
That means the disconnected port is not available to re-establish the connection again, so you have to use serialport.resume() method to re-enable the connection.
serialport.on('close', function(err){
console.log("Port closed.");
if(err.disconnected === true){
console.log("Disconnected!");
serialport.resume(function(e){
reconnectDevice(); // Serial Port Initialization Function. It's your method to declare serial port.
console.log("Error on resuming port:", e);
});
}
});
After that, it will automatically switch COM ports and you won't get error as 'Port Access denied.'.

Reconnect net.socket nodejs

I am new to node.js and would like to connect to a TCP socket. For this I am using the net module.
My idea was to wrap the connect sequence into a function then on the 'close' event, attempt a reconnection. Not that easy apparently.
function conn() {
client.connect(HOST_PORT, HOST_IP, function() {
startSequence();
})
}
client.on('close', function(e) {
log('info','Connection closed! -> ' + e)
client.destroy();
setTimeout(conn(),1000);
});
So when the remote host is closed, I see my logs comming through, howere what seems to be happening is that as soons as the remote host comes online ALL the previous attempts start to get processed - if that makes sense. If you look at client.connect, there is a function called startSequence that sends some data that "iniates" the connection from the remote server side. When the server goes offline and I start reconnecting all the failed attempts from before seem to have been buffered and are all sent together when the server goes online.
I have tried the code from this Stackoverflow link as well to no avail (Nodejs - getting client socket to try again after 5 sec time out)
client.connect(HOST_PORT, HOST_IP, function() {
pmsStartSequence();
})
// Add a 'close' event handler for the client socket
client.on('close', function(e) {
log('debug','connection closed -> ' + e)
client.setTimeout(10000, function() {
log('debug', 'trying to reconnect')
client.connect(HOST_PORT, HOST_IP, function() {
pmsStartSequence();
})
})
});
Is there any advice on how I can reconnect a socket after failure?
Inspired from the other solutions, I wrote this, it's tested, it works !
It will keep on trying every 5 sec, until connection is made, works if it looses connection too.
/* Client connection */
/* --------------------------------------------------------------------------------- */
const client = new net.Socket()
var intervalConnect = false;
function connect() {
client.connect({
port: 1338,
host: '127.0.0.1'
})
}
function launchIntervalConnect() {
if(false != intervalConnect) return
intervalConnect = setInterval(connect, 5000)
}
function clearIntervalConnect() {
if(false == intervalConnect) return
clearInterval(intervalConnect)
intervalConnect = false
}
client.on('connect', () => {
clearIntervalConnect()
logger('connected to server', 'TCP')
client.write('CLIENT connected');
})
client.on('error', (err) => {
logger(err.code, 'TCP ERROR')
launchIntervalConnect()
})
client.on('close', launchIntervalConnect)
client.on('end', launchIntervalConnect)
connect()
The problem is where you set the on-connect callback.
The doc of socket.connect() says:
connectListener ... will be added as a listener for the 'connect' event once.
By setting it in socket.connect() calls, every time you try reconnecting, one more listener (a one-time one), which calls startSequence(), is attached to that socket. Those listeners will not be fired until reconnection successes, so you got all of them triggered at the same time on a single connect.
One possible solution is separating the connect listener from socket.connect() calls.
client.on('connect', function() {
pmsStartSequence();
});
client.on('close', function(e) {
client.setTimeout(10000, function() {
client.connect(HOST_PORT, HOST_IP);
})
});
client.connect(HOST_PORT, HOST_IP);
My solution:
var parentHOST = '192.168.2.66';
var parentPORT = 9735;
var net = require('net');
var S = require('string');
var parentClient = new net.Socket();
var parentActive = false;
var startParentClient = function () {
parentClient = new net.Socket();
parentActive = false;
parentClient.connect(parentPORT, parentHOST, function() {
console.log('Connected ' + cluster.worker.id + ' to parent server: ' + parentHOST + ':' + parentPORT);
parentActive = true;
});
parentClient.on('error', function() {
parentActive = false;
console.log('Parent connection error');
});
parentClient.on('close', function() {
parentActive = false;
console.log('parent connection closed');
setTimeout(startParentClient(), 4000);
});
}
If is necessary connect:
if (!S(parentHOST).isEmpty() && !S(parentPORT).isEmpty()) {
startParentClient();
}
As mentioned multiple times in the comments, you need to use .removeAllListeners() before trying to reconnect your client to the server in order to avoid having multiple listeners on the same event.
The code below should do the trick
Note that I try to reconnect the client after the close and end events because these two events can be fired in different orders after closing a connection
const net = require("net")
let client = new net.Socket()
function connect() {
console.log("new client")
client.connect(
1337,
"127.0.0.1",
() => {
console.log("Connected")
client.write("Hello, server! Love, Client.")
}
)
client.on("data", data => {
console.log("Received: " + data)
})
client.on("close", () => {
console.log("Connection closed")
reconnect()
})
client.on("end", () => {
console.log("Connection ended")
reconnect()
})
client.on("error", console.error)
}
// function that reconnect the client to the server
reconnect = () => {
setTimeout(() => {
client.removeAllListeners() // the important line that enables you to reopen a connection
connect()
}, 1000)
}
connect()
I use the following code to achieve reconnection with node.js. I am not a Javascript expert so I guess it can be improved but it nevertheless works fine for me.
I hope this could help.
Best.
//----------------------------------------------------------------//
// SocketClient //
//----------------------------------------------------------------//
var net = require('net');
var SocketClient = function(host, port, data_handler, attempt)
{
var node_client;
var attempt_index = (attempt ? attempt : 1);
this.m_node_client = new net.Socket();
node_client = this.m_node_client;
this.m_node_client.on('close', function()
{
var new_wrapper = new SocketClient(host, port, data_handler, attempt_index + 1);
node_client.destroy();
new_wrapper.start();
});
this.m_node_client.on('data', data_handler);
this.m_node_client.on('error', function(data)
{
console.log("Error");
});
this.start = function()
{
this.m_node_client.connect(port, host, function()
{
console.log('Connected ' + attempt_index);
});
};
};
//----------------------------------------------------------------//
// Test //
//----------------------------------------------------------------//
var test_handler = function(data)
{
console.log('TestHandler[' + data + ']');
};
var wrapper = new SocketClient('127.0.0.1', 4000, test_handler);
wrapper.start();
I have tried re-using the same socket connection, by using this:
const s = net.createConnection({port});
s.once('end', () => {
s.connect({port}, () => {
});
});
that didn't work, from the server-side's perspective. If the client connection closes, it seems like a best practice to create a new connection:
const s = net.createConnection({port});
s.once('end', () => {
// create a new connection here
s = net.createConnection(...);
});
sad but true lulz.
Following this:
//
// Simple example of using net.Socket but here we capture the
// right events and attempt to re-establish the connection when
// is is closed either because of an error establishing a
// connection or when the server closes the connection.
//
// Requires
const net = require('net');
// Create socket
const port = 5555;
const host = '127.0.0.1';
const timeout = 1000;
let retrying = false;
// Functions to handle socket events
function makeConnection () {
socket.connect(port, host);
}
function connectEventHandler() {
console.log('connected');
retrying = false;
}
function dataEventHandler() {
console.log('data');
}
function endEventHandler() {
// console.log('end');
}
function timeoutEventHandler() {
// console.log('timeout');
}
function drainEventHandler() {
// console.log('drain');
}
function errorEventHandler() {
// console.log('error');
}
function closeEventHandler () {
// console.log('close');
if (!retrying) {
retrying = true;
console.log('Reconnecting...');
}
setTimeout(makeConnection, timeout);
}
// Create socket and bind callbacks
let socket = new net.Socket();
socket.on('connect', connectEventHandler);
socket.on('data', dataEventHandler);
socket.on('end', endEventHandler);
socket.on('timeout', timeoutEventHandler);
socket.on('drain', drainEventHandler);
socket.on('error', errorEventHandler);
socket.on('close', closeEventHandler);
// Connect
console.log('Connecting to ' + host + ':' + port + '...');
makeConnection();
function createServer() {
const client = new net.Socket();
client.connect(HOST_PORT, HOST_IP, function() {
console.log("Connected");
state = 1 - state;
client.write(state.toString());
});
client.on("data", function(data) {
console.log("Received: " + data);
//client.destroy(); // kill client after server's response
});
client.on("close", function() {
console.log("Connection closed");
//client.connect()
setTimeout(createServer, 2000);
});
}
createServer();

Nodejs: This socket is closed

I am using tcp sockets in Node.js to communicate with a Java client.
See my very basic server implementation below:
var server = my_http.createServer();
echo.installHandlers(server, {
prefix: '/echo'
});
server.listen(8000, '0.0.0.0');
var socketServer = net.createServer(function (socket) {
// Identify this client
socket.name = socket.remoteAddress + ":" + socket.remotePort
// Put this new client in the list
clients.push(socket);
sockets[socket.name] = socket;
// Handle incoming messages from clients.
socket.on('data', function (data) {
try {
var obj = JSON.parse(data);
if (obj.type == "sendMessage") {
broadcast("{\"id\":\"1\", \"msg\": \"" + obj.msg + "\", \"name\": \"" + obj.name + "\", \"time\": \"" + getDateTime() + "\"}\n", socket);
}
} catch (er) {
}
});
// Remove the client from the list when it leaves
socket.on('end', function () {
try {
clients.splice(clients.indexOf(socket), 1);
} catch (err) {
}
});
// Send a message
function broadcast(message, sender) {
try {
clients.forEach(function (client) {
client.write(message); // ERROR IS HERE
});
} catch (ee) {
}
}
return socket;
}).listen(8080);
For some reason sometimes i get this error:
events.js:71
throw arguments[1]; // Unhandled 'error' event
^
Error: This socket is closed.
Its happening on this line:
client.write(message); // ERROR IS HERE
Any ideas how to prevent this from happening?
or use client.writable to detect if the socket available.
clients.forEach(function (client) {
try {
if (client.writable) {
clientwrite("are u alive");
} else {
console.log('client is not writable');
}
} catch (err) {
console.log("cannot send message.");
}
}
Try this code
Option1
function broadcast(message, sender) {
try {
clients.forEach(function (client) {
if(client._handle){ // ensure there is still underlying handle
client.write(message); // ERROR IS HERE
}
});
} catch (ee) {
}
}
Option2
Attach error event listener on socket.
Socket.on('error',function(){
console.log("%j", arguments);
});

Active FTP Client for Node.js

I'm trying my hand at writing an ftp client against Filezilla that supports active mode using node.js. I'm new to ftp and node.js. I thought I could get a good understanding of tcp socket communication and the ftp protocol by doing this exercise. Also, node-ftp an jsftp don't seem to support active mode, so I think this will be a nice (though rarely used) addition to npm.
I've got some proof of concept code that works at least sometimes, but not all the time. In the case where it works, the client uploads a file called file.txt with the text 'hi'.
When it works, I get this:
220-FileZilla Server version 0.9.41 beta
220-written by Tim Kosse (Tim.Kosse#gmx.de)
220 Please visit http://sourceforge.net/projects/filezilla/
331 Password required for testuser
230 Logged on
listening
200 Port command successful
150 Opening data channel for file transfer.
server close
226 Transfer OK
half closed
closed
Process finished with exit code 0
When it doesn't work, I get this:
220-FileZilla Server version 0.9.41 beta
220-written by Tim Kosse (Tim.Kosse#gmx.de)
220 Please visit http://sourceforge.net/projects/filezilla/
331 Password required for testuser
230 Logged on
listening
200 Port command successful
150 Opening data channel for file transfer.
server close
half closed
closed
Process finished with exit code 0
So, I'm not getting the 226, and I'm not sure why I'm getting the inconsistent results.
Forgive the poorly written code. I'll refactor once I'm confident I understand how this should work.:
var net = require('net'),
Socket = net.Socket;
var cmdSocket = new Socket();
cmdSocket.setEncoding('binary')
var server = undefined;
var port = 21;
var host = "localhost";
var user = "testuser";
var password = "Password1*"
var active = true;
var supplyUser = true;
var supplyPassword = true;
var supplyPassive = true;
var waitingForCommand = true;
var sendFile = true;
function onConnect(){
}
var str="";
function onData(chunk) {
console.log(chunk.toString('binary'));
//if ftp server return code = 220
if(supplyUser){
supplyUser = false;
_send('USER ' + user, function(){
});
}else if(supplyPassword){
supplyPassword = false;
_send('PASS ' + password, function(){
});
}
else if(supplyPassive){
supplyPassive = false;
if(active){
server = net.createServer(function(socket){
console.log('new connection');
socket.setKeepAlive(true, 5000);
socket.write('hi', function(){
console.log('write done');
})
socket.on('connect', function(){
console.log('socket connect');
});
socket.on('data', function(d){
console.log('socket data: ' + d);
});
socket.on('error', function(err){
console.log('socket error: ' + err);
});
socket.on('end', function() {
console.log('socket end');
});
socket.on('drain', function(){
console.log('socket drain');
});
socket.on('timeout', function(){
console.log('socket timeout');
});
socket.on('close', function(){
console.log('socket close');
});
});
server.on('error', function(e){
console.log(e);
});
server.on('close', function(){
console.log('server close');
});
server.listen(function(){
console.log('listening');
var address = server.address();
var port = address.port;
var p1 = Math.floor(port/256);
var p2 = port % 256;
_sendCommand('PORT 127,0,0,1,' + p1 + ',' + p2, function(){
});
});
}else{
_send('PASV', function(){
});
}
}
else if(sendFile){
sendFile = false;
_send('STOR file.txt', function(){
});
}
else if(waitingForCommand){
waitingForCommand = false;
cmdSocket.end(null, function(){
});
if(server)server.close(function(){});
}
}
function onEnd() {
console.log('half closed');
}
function onClose(){
console.log('closed');
}
cmdSocket.once('connect', onConnect);
cmdSocket.on('data', onData);
cmdSocket.on('end', onEnd);
cmdSocket.on('close', onClose);
cmdSocket.connect(port, host);
function _send(cmd, callback){
cmdSocket.write(cmd + '\r\n', 'binary', callback);
}
Also, is the server appropriate, or should I do it some other way?
EDIT:
I changed the callback in server.listen to use a random port. This has removed the 425 I was getting previously. However, I am still not getting consistent behavior with the file transfer.
The flow for Active mode FTP transfers goes roughly like this:
Connection preamble (USER/PASS)
Establish a client local socket for data
Inform the server of that socket (PORT)
Tell the server to open the remote file for writing (STOR)
Start writing the data from the data socket established above (socket.write())
Close the stream from the client side (socket.end()) to end the file transfer
Tell the server you are done (QUIT)
Clean up any open sockets and servers on the client
So once you've done this:
else if(sendFile){
sendFile = false;
_send('STOR file.txt', function(){
});
}
The server will respond with a 150 saying it has connected to the data socket you established and is ready to receive data.
One improvement to make it easier to reason about the execution at this point would be to change your control flow to operate on a parsed response code rather than pre-defined bools.
function onData(chunk) {
console.log(chunk);
var code = chunk.substring(0,3);
if(code == '220'){
instead of:
function onData(chunk) {
console.log(chunk.toString('binary'));
//if ftp server return code = 220
if(supplyUser){
Then you can add a section for sending the data:
//ready for data
else if (code == '150') {
dataSocket.write('some wonderful file contents\r\n', function(){});
dataSocket.end(null, function(){});
}
And a little more to clean up:
//transfer finished
else if ( code == '226') {
_send('QUIT', function(){ console.log("Saying Goodbye");});
}
//session end
else if ( code == '221') {
cmdSocket.end(null, function(){});
if(!!server){ server.close(); }
}
Obviously things will get more complicated if you are sending multiple files etc, but this should get your proof of concept running more reliably:
var net = require('net');
Socket = net.Socket;
var cmdSocket = new Socket();
cmdSocket.setEncoding('binary')
var server = undefined;
var dataSocket = undefined;
var port = 21;
var host = "localhost";
var user = "username";
var password = "password"
var active = true;
function onConnect(){
}
var str="";
function onData(chunk) {
console.log(chunk.toString('binary'));
var code = chunk.substring(0,3);
//if ftp server return code = 220
if(code == '220'){
_send('USER ' + user, function(){
});
}else if(code == '331'){
_send('PASS ' + password, function(){
});
}
else if(code == '230'){
if(active){
server = net.createServer(function(socket){
dataSocket = socket;
console.log('new connection');
socket.setKeepAlive(true, 5000);
socket.on('connect', function(){
console.log('socket connect');
});
socket.on('data', function(d){
console.log('socket data: ' + d);
});
socket.on('error', function(err){
console.log('socket error: ' + err);
});
socket.on('end', function() {
console.log('socket end');
});
socket.on('drain', function(){
console.log('socket drain');
});
socket.on('timeout', function(){
console.log('socket timeout');
});
socket.on('close', function(){
console.log('socket close');
});
});
server.on('error', function(e){
console.log(e);
});
server.on('close', function(){
console.log('server close');
});
server.listen(function(){
console.log('listening');
var address = server.address();
var port = address.port;
var p1 = Math.floor(port/256);
var p2 = port % 256;
_send('PORT 127,0,0,1,' + p1 + ',' + p2, function(){
});
});
}else{
_send('PASV', function(){
});
}
}
else if(code == '200'){
_send('STOR file.txt', function(){
});
}
//ready for data
else if (code == '150') {
dataSocket.write('some wonderful file contents\r\n', function(){});
dataSocket.end(null, function(){});
}
//transfer finished
else if ( code == '226') {
_send('QUIT', function(){ console.log("Saying Goodbye");});
}
//session end
else if ( code == '221') {
cmdSocket.end(null, function(){});
if(!!server){ server.close(); }
}
}
function onEnd() {
console.log('half closed');
}
function onClose(){
console.log('closed');
}
cmdSocket.once('connect', onConnect);
cmdSocket.on('data', onData);
cmdSocket.on('end', onEnd);
cmdSocket.on('close', onClose);
cmdSocket.connect(port, host);
function _send(cmd, callback){
cmdSocket.write(cmd + '\r\n', 'binary', callback);
}

Resources