主动FTP客户端为Node.js

我正在努力编写一个针对Filezilla的ftp客户端,使用node.js支持主动模式。 我是新来的ftp和node.js。 我想通过这个练习,我可以很好地理解TCP套接字通信和ftp协议。 而且, node-ftp和jsftp似乎不支持活动模式,所以我认为这将是一个不错的(虽然很less使用)除了npm。

我有一些至less有时可用的概念代码的certificate,但不是所有的时间。 在工作的情况下,客户端用文本“hi”上传一个名为file.txt的文件。 当它的工作,我得到这个:

 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 

当它不起作用,我得到这个:

 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 

所以,我没有得到226,我不知道为什么我得到了不一致的结果。

原谅写得不好的代码。 一旦我有信心,我会重构,我明白这应该如何工作:

 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); } 

另外, server合适,或者我应该采取其他方式吗?

编辑:我改变了server.listencallback使用随机端口。 这已经删除了我以前得到的425。 但是,我仍然没有得到与文件传输一致的行为。

主动模式FTP传输的stream程大致如下:

  • 连接前导码( USER / PASS
  • 为数据build立一个客户端本地套接字
  • 通知该套接字的服务器( PORT
  • 告诉服务器打开远程文件进行写入( STOR
  • 开始从上面build立的数据套接字( socket.write() )写入数据
  • closures客户端的stream( socket.end() )以结束文件传输
  • 告诉服务器你已经完成( QUIT
  • 清理客户端上的任何打开的套接字和服务器

所以一旦你这样做了:

 else if(sendFile){ sendFile = false; _send('STOR file.txt', function(){ }); } 

服务器将回应一个150说它已连接到您build立的数据套接字,并准备好接收数据。

在这一点上更容易推断执行的一个改进是将您的控制stream程更改为对已parsing的响应代码进行操作,而不是对预定义的bools进行操作。

 function onData(chunk) { console.log(chunk); var code = chunk.substring(0,3); if(code == '220'){ 

代替:

 function onData(chunk) { console.log(chunk.toString('binary')); //if ftp server return code = 220 if(supplyUser){ 

然后你可以添加一个部分发送数据:

 //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(); } } 

如果你发送多个文件等等,显然事情会变得更加复杂,但是这应该让你的概念validation运行更可靠:

 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); }