FTPクライアントをNodejsで作る。JavaScript非同期プログラミングが光る!

プログラミング

こんにちは!

仕事では最先端のソフト開発を行っているのに、私自身は全くコーディングせず、もっぱら週末にJavaScriptに精を出す、自称「戦う週末プログラマー」のしずかなかずしです。

突然ですが、FTPプロトコルって使っていますか?

今時FTPなんて使わないと思っているあなた。実はネットでFTP関連の検索をすると意外と用途が残っているものです。例えば、もしあなたがブログを書くのにWordpress を使っているのなら、Wordpress をポスティングしているレンタルサーバー上でファイル操作をしたくなることでしょう。

そんな時はFTP を使ってファイル転送したくなるはずです。

FTP クライアントはGUI のツールが色々存在しますが、プログラムで自動化したい、そんなあなたに送るのが本日のテーマ。Nodejs で FTPクライアントを実現してみます。

広告

FTPとは?

FTP(File Transfer Protocol)とは、ファイル転送を扱うインターネットの接続手順(プロトコル)です。サーバー側は、ファイルシステムのディレクトリ同様の階層構造を想定しており、階層構造を操作したり、階層上の特定の位置へファイルをアップロードしたり、ダウンロードするなどといったやり取りを行うためのものです。サーバーがストレージを提供し、クライアントがリモートにあるストレージを操作する、という関係のクライアント・サーバー・システムを実現するプロトコルです。

その歴史は古く、Wikipediaによると1980年代にインターネット(TCP/IP)のプロトコルとして定義されているもので、実に30年以上前からある、化石のようなプロトコルです。

ちなみに、「FTPプロトコル」言い方をすることがありますが、略称が「File Transfer Protocol(プロトコル)」を略したものなので、「FTPプロトコル」というと「プロトコル・プロトコル」と二重表現になってしまいます。韓国料理でお馴染み「チゲ鍋」の「チゲ」が韓国語で「鍋」料理のことなで、「チゲ鍋」という表現が「なべ・なべ」と2重になっているのと同じですね。

「FTP」とだけ記載するとサーバーのことを言っているのかクライアントのことを言っているのか、はたまたプロトコル自体の話をしているのか分かりにくくなります。ですので以下の記事では、慣例に従ってプロトコルの話をする時は二重表現を使うことにします。

PassiveとActiveに注意

FTPプロトコルにはPassiveActiveという2つのモードがあります。
通常、FTPクライアントがFTPサーバーにアクセスする際(ログインするとき)にどちらかに決めて通信を始めます。

FTPプロトコルでは、ディレクトリ一覧をクライアントに送るとき、または、ファイルのダウンロードときなど、いくつかのコマンドでサーバープログラム側からクライアントに向かってTCPのコネクションを張るケースがあります(下図)。

こういったプロトコルの仕様は、一部の環境では正しく動作しません。それは、クライアントがホームルータ内にある場合。別の言い方をすると、インターネット接続に対してNATが使われている場合です。また、クライアントマシン自体がファイヤーウォールを設定している場合にもうまく機能しません。言い換えると、FTPサーバーからFTPクライアントが待機(listen)しているポートにアクセスができないようなネットワークの環境です。このような環境ではサーバーからのパケットがクライアントに到達しないため、FTPプロトコルが期待通りに動かないのです。

このような環境でもFTPを動作させるためには、NATでFTPが使うポートを適切にルーティング設定する、または、ファイアウォールでFTPが使うポートを開放するといった特殊対応が必要になります。

幸いな事に、特殊設定を施すことなくこの問題を解決するオプションが、実はプロトコル自体に用意されています。それが、Passiveモードです。

先に挙げたような別なTCPコネクションが必要なコマンドで、サーバープログラム側からクライアントにコネクションを張るのではなく、クライアントからサーバーに向かってコネクションを張るのです。これなら、クライアントとサーバーが一般なWebプロトコル(HTTP/HTTPS)と同様の関係になるので、NAT問題やファイアウォールの問題は発生しません。

ちなみに、Windowsのコマンドラインftp.exeはPassiveモードをサポートしていない、という記事を見かけます。ホームルータを使って家庭内LANからインターネット上にアクセスする環境では、NATが一般的ですので、こういった環境が多い昨今では、ほとんど使い物にならないですね。

FTPクライアントモジュールnode-ftp

では、JavaScriptでプログラミングしてみます。

使用するモジュールは、その名もズバリ「ftp」(node-ftp)です。以下のようにインストールします。

npm install ftp

Nodejsのftpクライアント関連のライブラリはいくつか公開されているものがあります。例えば、「basic-ftp」です。しかし「ftp」の良いところは、FTPプロトコルで定義されているコマンドに比較的判りやすくマッピングされているポイントです。例えば、ディレクトリ内部のファイル一覧を取得したければ、listという関数を使えばよいし、ディレクトリ構造内で移動したければ、cwd(change working directory)という関数を呼び出せばよいです。

ftpクライアントのコマンドラインプログラムに精通している方であれば、コマンドラインで実行できるコマンドがどんなものがあるかある程度解ると思います。「ftp」モジュールで定義されている関数は、このコマンド単位でライブラリの関数が定義されているイメージで、非常に判りやすく使えるのです。

そして関数呼び出しは全て「非同期」で結果が返ります。つまり、関数を呼び出す際にかならず結果を受け取るコールバック関数を指定します。

例えば、現在のワーキング・ディレクトリを取得したければ関数pwd()を呼び出すのですが、以下のように非同期で結果を受け取ることができます。ここで、cはftpモジュールから生成したftpクライアントクラスのインスタンスです。

  c.pwd(function(err, cwd) {
    if (err) {
      console.log('error: ' + err.message);
    } else {
      console.log(`"${cwd}" is current directory`);
    }
  });

node-ftpモジュールはPassiveモードでしか使えないようですが、通常の環境であれば困ることはないでしょう。

サンプルとして、ftpコマンドラインプログラム同様の動きをするftpクライアントアプリを書いてみました。かなり雑なプログラムですが、node-ftpモジュールの使い方がわかるのではないでしょうか。このプログラムは起動すると、ユーザ入力を待ち、ftpコマンドラインプログラム同様、cd, ls, bin, mkdir, pwd, lcd, getというFTPコマンドを実行すうることができます。

connect()を呼び出すコード(17行目付近)にFTPサーバーアドレス、ポート、ユーザアカウント、パスワードを決め打ちで書いているので、実際に使う場合は、ご自分の環境に合せて書き換えてみて下さい。

今どきなJSが良ければpromise-ftp

node-ftpモジュールは、関数定義が全てコールバック関数で結果を返します。

一方、最近のJavaScriptの規格(ES6, 2015)では、非同期で結果を受け取るためにPromiseという仕組みが導入されています。JavaScript関数の結果を非同期で受けとる形式はPromiseを使うのが最近のトレンドです。Promise形式の関数であれば、await/asyncキーワードを使って非同期関数を同期関数処理のようにコーディングすることが可能になるので便利です(MDN Web Docsのawait参照)。

以前、こちらのブログしずかなかずしでも、非同期関数をPromiseで受け取るように書き換える場合のサンプルを説明しました。詳しくは以下の記事に讓りますが、コールバックタイプの関数をPromiseを使った今風の関数に書き換えるのは単純な話です。

幸い、上記のnode-ftpをPromiseタイプの関数で定義し直してくれる「promise-ftp」というライブラリを公開している方がいます。

今どきのJavaScriptでプログラミングしたいんだ!」という方はpromise-ftpを使ってみても良いでしょう。

サーバーの知識やTCPをより深く理解したい方は、以下の書籍などはいかがでしょう?

created by Rinker
¥4,390 (2021/09/20 15:40:39時点 Amazon調べ-詳細)