FTPサーバーをpythonで立てる
pythonでFTPサーバーをローカルで構築し、ファイルの読み取りとアップロードを体験してもらいます。
約30分で、Pythonのpyftpdlib を使って自分のマシンに FTPサーバーを立て、ファイル転送と権限制御を体験します。
はじめに:ゴールと「なぜ今 FTP?」
このハンズオンのゴールは次の3つです。
①自分のマシンでFTPサーバーを起動し、クライアントから接続してファイルを転送する
②「サーバーを立てる」とはプログラム的に何をすることなのかを体感する
③権限を1文字単位で付け外しして、認可(何が許されるか)の仕組みを実験する
先に正直に言っておくと、実務のファイル転送は今やSFTPやクラウドストレージが主流で、素のFTPを新規採用する場面はほぼありません。それでも教材としてFTPを選ぶ理由は、プロトコルが人間に読めるテキストだからです。サーバーとクライアントの会話をそのまま目で追える通信プロトコルは貴重で、「サーバーとは何か」を理解する最短ルートになります。ここで掴んだ感覚は、HTTPやWebSocketを学ぶときにそのまま効いてきます。
FTPの仕組み:コネクションは2本ある
FTPの最大の特徴は、コマンド用とデータ転送用でコネクションを2本使い分けることです。
制御コネクションでは「USER student」「LIST」のようなテキストコマンドと「230 Login successful.」のような応答が往復し、ファイルの中身は別途張られるデータコネクションを流れます。
データコネクションの張り方には、2方式 あります。
• アクティブモード:サーバーからクライアントへ接続しに行く。クライアント側のファイアウォールに弾かれやすく、現在はほぼ使われない
• パッシブモード:サーバーが「このポートに接続してね」と番号を教え、クライアントから接続する。今日はこちらを使う。 「接続はできるのにファイル一覧で固まる」という FTPで一番有名なトラブルは、「制御コネクションは通ったがデータコネクションが張れていない」状態です。
この2本構造を知っていると、後半のトラブルシューティングが自力でできるようになります。
事前準備(5分)
必要なものは、2つだけです。
①Python 3.11(動作確認済みバージョン)
python --version # Python 3.11.x のように出ればpythonがインストールされています。
②pyftpdlib のインストール
pip install pyftpdlib
③作業フォルダを、2つ作る
サーバーが公開するフォルダと、クライアントの作業場所を別フォルダにします。同じフォルダでgetすると、公開フォルダと同じ場所にダウンロードされて「ファイルが移動したように見えない」ため、転送の実感が出ないからです。
workshop/ ├── server/ │ ├── server.py ← この後書くコード │ └── ftp_root/ ← サーバーが公開する側 │ └── sample.txt └── client/ ← クライアントの作業場所 └── upload_me.txt ← アップロード実験用
以下を実行するか、手動でファイルを作成してください。
mkdir -p workshop/server/ftp_root workshop/client echo "hello ftp" > workshop/server/ftp_root/sample.txt echo "upload test" > workshop/client/upload_me.txt
コード全体(完成形)
まず完成形を見てください。たったこれだけでFTPサーバーが動きます。server.pyとして保存します。
# server.py from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer # 1. 誰がログインできて、何を許すか(認証と認可) authorizer = DummyAuthorizer() authorizer.add_user("student", "pass123", "./ftp_root", perm="elradfmw") authorizer.add_anonymous("./ftp_root") # 匿名ユーザー(読み取り専用) # 2. コネクションをどう扱うか handler = FTPHandler handler.authorizer = authorizer handler.passive_ports = range(60000, 60100) # データコネクション用のポート範囲 # 3. どこで待ち受けるか server = FTPServer(("127.0.0.1", 2121), handler) server.serve_forever()
起動は次の 1 行です。
python server.py
「サーバーを立てる」の正体は、待ち受け場所(IPとポート)を決めて、無限ループで接続を待つプログラムを動かすこと。serve_forever() という名前がそのまま本質を表しています。
コード解説:3つのブロック
ブロック1:Authorizer =「誰に何を許すか」
authorizer.add_user("student", "pass123", "./ftp_root", perm="elradfmw")
引数は順に、ユーザー名・パスワード・ホームディレクトリ(このユーザーに見せるフォルダ)・権限です。注目してほしいのが perm="elradfmw"。権限が1文字、1操作で表現されています。
| 文字 | 許可される操作 | FTPコマンド例 |
|---|---|---|
| e | ディレクトリ移動 | CWD |
| l | ファイル一覧の取得 | LIST |
| r | ダウンロード | RETR |
| a | 追記アップロード | APPE |
| d | 削除 | DELE |
| f | リネーム | RNFR / RNTO |
| m | ディレクトリ作成 | MKD |
| w | アップロード | STOR |
elradfmwはフル権限、elrなら「見る・落とす」だけの読み取り専用ユーザーになります。後半の演習でここをいじります。
ブロック2:Handler =「1 接続をどう処理するか」
handler = FTPHandler handler.authorizer = authorizer handler.passive_ports = range(60000, 60100)
クライアント1接続ごとの振る舞いを決めるクラスです。passive_portsは、データコネクション用にサーバーが開くポートの範囲。指定しないとOS任せのランダムなポートになり、ファイアウォール環境で「一覧取得だけ固まる」事故の原因になります。
ブロック3:Server =「どこで待ち受けるか」
server = FTPServer(("127.0.0.1", 2121), handler)
127.0.0.1 は「自分のマシンからの接続のみ受け付ける」という意味です。ポートが標準の21ではなく、2121 なのは、1023以下のポートは管理者権限が必要だから。開発ではこうして1024以上に逃がすのが定番です。
起動して接続する
サーバーもクライアントも自分のPCで動かすので、ターミナルを2枚開きます。1枚目がサーバー役。起動したら放置して、ログ観察用の窓にします。
# ターミナル1(サーバー役) cd workshop/server python server.py
ログが流れ始めたら待ち受け成功です。別ターミナル(またはFileZilla)から接続します。
ftp コマンドの場合(2 枚目のターミナル):
# ターミナル2(クライアント役) cd workshop/client # ← クライアント側フォルダから起動するのがポイント ftp 127.0.0.1 2121 # Name: student # Password: pass123 ftp> ls # ファイル一覧(sample.txt が見えるはず) ftp> get sample.txt # → client/ にダウンロードされる ftp> put upload_me.txt # → server/ftp_root/ に現れる ftp> quit
接続した瞬間、サーバー側のターミナルに接続ログが流れるのを必ず見てください。自分の操作がリアルタイムでサーバーに届いている様子が見えるのが、このハンズオンで一番大事な瞬間です。ftp_root フォルダを開いて、アップロードしたファイルが実際に置かれていることも確認しましょう。
転送が終わったら、エクスプローラー / Finderで client/ と server/ftp_root/ を並べて見てください。client/ には sample.txt が増え、ftp_root/ には upload_me.txt が増えているはずです。同一PC内なのに、ネットワーク越しとまったく同じプロトコルでファイルが行き来した。
演習:権限を削って「できない」を体験する
ここからが本番です。権限文字を操作して、認可の仕組みを実験します。
演習1:読み取り専用ユーザーにする
server.py の権限を書き換えて再起動します。
authorizer.add_user("student", "pass123", "./ftp_root", perm="elr")
再接続してputを試すと:
550 Not enough privileges.
ダウンロード(get)は成功するのに、アップロード(put)だけが拒否されます。エラーコード550 と「権限不足」の対応も確認してください。
演習2:一覧も見せない
perm="er" # l を削る
今度は ls が失敗します。「接続・ログインはできるのに一覧が取れない」という状態を自分で作れたことになります。
演習3(時間があれば):2人目のユーザーを追加
authorizer.add_user("admin", "admin123", "./ftp_root", perm="elradfmw") authorizer.add_user("guest", "guest123", "./ftp_root", perm="elr")
同じフォルダに対して、ユーザーごとに許される操作が違う——これが実務の「ロール設計」の原型です。
この演習のポイントは、認証(誰か=ログイン)と認可(何を許すか=perm)が別物だと体で理解すること。Web 開発でも JWT やセッションで「認証」した後に「認可」で弾く、という同じ2段構えが出てきます。