9

node.jsを使用してアクティブモードをサポートするFilezillaに対してftpクライアントを作成しようとしています。私はftpとnode.jsが初めてです。この演習を行うことで、tcp ソケット通信と ftp プロトコルについて十分に理解できると思いました。また、node-ftpjsftpはアクティブ モードをサポートしていないようです。そのため、これは (めったに使用されませんが) npm への優れた追加機能になると思います。

私は、少なくとも時々は機能するが、常に機能するわけではない概念実証コードをいくつか持っています。動作する場合、クライアントはfile.txt「hi」というテキストを含むファイルをアップロードします。それが機能すると、次のようになります。

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.listen のコールバックをランダムなポートを使用するように変更しました。これにより、以前取得していた 425 が削除されました。ただし、ファイル転送で一貫した動作が得られません。

4

1 に答える 1

1

アクティブ モードの FTP 転送のフローは、おおよそ次のようになります。

  • 接続プリアンブル ( USER/ PASS)
  • データ用のクライアント ローカル ソケットを確立する
  • サーバーにそのソケットを通知する ( PORT)
  • 書き込み用にリモート ファイルを開くようにサーバーに指示する ( STOR)
  • 上記で確立したデータ ソケットからデータの書き込みを開始します ( socket.write())
  • クライアント側からのストリームを閉じて ( socket.end())、ファイル転送を終了します
  • サーバーに完了を伝える ( QUIT)
  • クライアントで開いているソケットとサーバーをクリーンアップします

したがって、これを行ったら:

else if(sendFile){
    sendFile = false;

    _send('STOR file.txt', function(){

    });
}

150サーバーは、確立したデータ ソケットに接続され、データを受信する準備ができているという応答を返します。

この時点での実行についての推論を容易にする 1 つの改善点は、事前定義された bool ではなく、解析された応答コードで動作するように制御フローを変更することです。

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

複数のファイルなどを送信する場合は明らかに複雑になりますが、これにより概念実証がより確実に実行されるはずです。

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);
}
于 2016-08-15T18:01:19.550 に答える