Symfoware

Symfowareについての考察blog

Python + pyftpdlibでカスタムFTPサーバーを作成3 ファイルの取得

pyftpdlibでFTPサーバーをカスタムしてみています。
Python + pyftpdlibでカスタムFTPサーバーを作成2 ディレクトリの表示

Javaでやったときと同様に、任意のファイルをダウンロードするようにしてみます。
Java + MockFtpServerでFTPサーバー側のプログラムを作成する その3



固定ファイルのダウンロード



何を選んでも同じファイルをダウンロードさせる最小構成のサンプルです。


  1. # -*- coding:utf-8 -*-
  2. from pyftpdlib.authorizers import DummyAuthorizer
  3. from pyftpdlib.authorizers import AuthenticationFailed
  4. from pyftpdlib.handlers import FTPHandler
  5. from pyftpdlib.servers import FTPServer
  6. from pyftpdlib.filesystems import AbstractedFS
  7. class MyAuthorizer(object):
  8.     
  9.     # ユーザー名、パスワードで認証
  10.     def validate_authentication(self, username, password, handler):
  11.         
  12.         msg = "Authentication failed."
  13.         if username != 'symfo':
  14.             raise AuthenticationFailed(msg)
  15.         
  16.         if password != 'pass':
  17.             raise AuthenticationFailed(msg)
  18.     
  19.     # ユーザーのホームディレクトリを返す
  20.     def get_home_dir(self, username):
  21.         import os
  22.         homedir = '.'
  23.         return os.path.realpath(homedir).decode('utf8')
  24.     
  25.     # アクセス権限チェック
  26.     def has_perm(self, username, perm, path=None):
  27.         return 'elr'
  28.     
  29.     def get_perms(self, username):
  30.         return 'elr'
  31.     
  32.     # ログイン時に表示するメッセージ
  33.     def get_msg_login(self, username):
  34.         return 'Login successful.'
  35.     
  36.     # ログアウト時に表示するメッセージ
  37.     def get_msg_quit(self, username):
  38.         return 'Goodbye.'
  39.         
  40.     # ファイルにアクセスする前のフック
  41.     def impersonate_user(self, username, password):
  42.         pass
  43.     
  44.     # ファイルにアクセスした後のフック
  45.     def terminate_impersonation(self, username):
  46.         pass
  47.         
  48. class MyFileSystem(object):
  49.     
  50.     def __init__(self, root, cmd_channel):
  51.         self._cwd = u'/'
  52.         self._root = root
  53.         self.cmd_channel = cmd_channel
  54.     
  55.     # 現在のカレントディレクトリ
  56.     @property
  57.     def cwd(self):
  58.         return self._cwd
  59.         
  60.     # ftp要求パスをリアルパスに変換
  61.     def ftp2fs(self, ftppath):
  62.         return u'.'
  63.     
  64.     # 要求パスの妥当性検証
  65.     def validpath(self, path):
  66.         return True
  67.     
  68.     
  69.     # 指定されたパスがディレクトリであるか
  70.     def isdir(self, path):
  71.         print path
  72.         return True
  73.     
  74.     # ディレクトリの内容をリストアップ
  75.     def listdir(self, path):
  76.         # ダミーの内容を返す
  77.         return [u'1', u'2']
  78.     
  79.     # フォーマット済の内容を返す
  80.     def format_mlsx(self, basedir, listing, perms, facts, ignore_err=True):
  81.         
  82.         line = u'type=dir;size=0;perm=el;modify=20071127230206; 1\r\n'
  83.         yield line.encode('utf8', self.cmd_channel.unicode_errors)
  84.         
  85.         line = u'type=file;size=202103;perm=r;modify=20071029155301; 2.png\r\n'
  86.         yield line.encode('utf8', self.cmd_channel.unicode_errors)
  87.     
  88.     def chdir(self, path):
  89.         pass
  90.         
  91.     def open(self, filename, mode):
  92.         return open('sample.png', 'rb')
  93.     
  94.     
  95.     
  96. def main():
  97.     
  98.     # 認証用のオブジェクトを作成
  99.     authorizer = MyAuthorizer()
  100.     #authorizer = DummyAuthorizer()
  101.     # id:user,pass:12345というユーザーを作成 操作権限(perm)は全て与える
  102.     #authorizer.add_user('symfo', 'pass', '.', perm='elradfmwM')
  103.     # FTP通信のハンドラーを作成 ユーザー認証オブジェクトを設定する
  104.     handler = FTPHandler
  105.     handler.authorizer = authorizer
  106.     # 接続時に表示される文字列指定
  107.     handler.banner = "pyftpdlib based ftpd ready."
  108.     
  109.     # 使用するファイルシステム
  110.     handler.abstracted_fs = MyFileSystem
  111.     # ftpサーバーのポートを指定
  112.     address = ('localhost', 10021)
  113.     server = FTPServer(address, handler)
  114.     # サーバー起動
  115.     server.serve_forever()
  116. if __name__ == '__main__':
  117.     main()



ファイル名はちゃんと拡張子付きで表示するのがポイントです。
FileZilla等のクライアントツールは、拡張子により
バイナリモードとASCIIモードを切り替えるようで、
適当な拡張子だと、ASCIIモードでダウンロードされ、ファイルが壊れます。






StringIOによる実装



openで、ファイルオブジェクトを返却するとそのファイルのダウンロードが行われます。
ファイルの実態がない(http経由で取得やデータベースのblobに格納)の場合を考え、
openで、StringIOオブジェクトを返すことを考えてみました。

変更箇所はopenメソッドのみです。


  1. # -*- coding:utf-8 -*-
  2. from pyftpdlib.authorizers import DummyAuthorizer
  3. from pyftpdlib.authorizers import AuthenticationFailed
  4. from pyftpdlib.handlers import FTPHandler
  5. from pyftpdlib.servers import FTPServer
  6. from pyftpdlib.filesystems import AbstractedFS
  7. from cStringIO import StringIO
  8. class MyAuthorizer(object):
  9.     
  10.     # ユーザー名、パスワードで認証
  11.     def validate_authentication(self, username, password, handler):
  12.         
  13.         msg = "Authentication failed."
  14.         if username != 'symfo':
  15.             raise AuthenticationFailed(msg)
  16.         
  17.         if password != 'pass':
  18.             raise AuthenticationFailed(msg)
  19.     
  20.     # ユーザーのホームディレクトリを返す
  21.     def get_home_dir(self, username):
  22.         import os
  23.         homedir = '.'
  24.         return os.path.realpath(homedir).decode('utf8')
  25.     
  26.     # アクセス権限チェック
  27.     def has_perm(self, username, perm, path=None):
  28.         return 'elr'
  29.     
  30.     def get_perms(self, username):
  31.         return 'elr'
  32.     
  33.     # ログイン時に表示するメッセージ
  34.     def get_msg_login(self, username):
  35.         return 'Login successful.'
  36.     
  37.     # ログアウト時に表示するメッセージ
  38.     def get_msg_quit(self, username):
  39.         return 'Goodbye.'
  40.         
  41.     # ファイルにアクセスする前のフック
  42.     def impersonate_user(self, username, password):
  43.         pass
  44.     
  45.     # ファイルにアクセスした後のフック
  46.     def terminate_impersonation(self, username):
  47.         pass
  48.         
  49. class MyFileSystem(object):
  50.     
  51.     def __init__(self, root, cmd_channel):
  52.         self._cwd = u'/'
  53.         self._root = root
  54.         self.cmd_channel = cmd_channel
  55.     
  56.     # 現在のカレントディレクトリ
  57.     @property
  58.     def cwd(self):
  59.         return self._cwd
  60.         
  61.     # ftp要求パスをリアルパスに変換
  62.     def ftp2fs(self, ftppath):
  63.         return u'.'
  64.     
  65.     # 要求パスの妥当性検証
  66.     def validpath(self, path):
  67.         return True
  68.     
  69.     
  70.     # 指定されたパスがディレクトリであるか
  71.     def isdir(self, path):
  72.         print path
  73.         return True
  74.     
  75.     # ディレクトリの内容をリストアップ
  76.     def listdir(self, path):
  77.         # ダミーの内容を返す
  78.         return [u'1', u'2']
  79.     
  80.     # フォーマット済の内容を返す
  81.     def format_mlsx(self, basedir, listing, perms, facts, ignore_err=True):
  82.         
  83.         line = u'type=dir;size=0;perm=el;modify=20071127230206; 1\r\n'
  84.         yield line.encode('utf8', self.cmd_channel.unicode_errors)
  85.         
  86.         line = u'type=file;size=202103;perm=r;modify=20071029155301; 2.png\r\n'
  87.         yield line.encode('utf8', self.cmd_channel.unicode_errors)
  88.     
  89.     def chdir(self, path):
  90.         pass
  91.         
  92.     def open(self, filename, mode):
  93.         #return open('sample.png', 'rb')
  94.         
  95.         # nameプロパティが必要なのでラップする
  96.         class FileWrapper(object):
  97.             def __init__(self, name):
  98.                 self.file = StringIO()
  99.                 with open(name, 'rb') as f:
  100.                     self.file.write(f.read())
  101.                     self.file.seek(0)
  102.                     
  103.                 self.name = name
  104.             def __getattr__(self, attr):
  105.                 return getattr(self.file, attr)
  106.         return FileWrapper('sample.png')
  107.     
  108.     
  109. def main():
  110.     
  111.     # 認証用のオブジェクトを作成
  112.     authorizer = MyAuthorizer()
  113.     #authorizer = DummyAuthorizer()
  114.     # id:user,pass:12345というユーザーを作成 操作権限(perm)は全て与える
  115.     #authorizer.add_user('symfo', 'pass', '.', perm='elradfmwM')
  116.     # FTP通信のハンドラーを作成 ユーザー認証オブジェクトを設定する
  117.     handler = FTPHandler
  118.     handler.authorizer = authorizer
  119.     # 接続時に表示される文字列指定
  120.     handler.banner = "pyftpdlib based ftpd ready."
  121.     
  122.     # 使用するファイルシステム
  123.     handler.abstracted_fs = MyFileSystem
  124.     # ftpサーバーのポートを指定
  125.     address = ('localhost', 10021)
  126.     server = FTPServer(address, handler)
  127.     # サーバー起動
  128.     server.serve_forever()
  129. if __name__ == '__main__':
  130.     main()




nameプロパティが必要なので、StringIOをラップしてやりました。
writeした後、ちゃんとseek(0)していないと0Byteのファイルがダウンロードされちゃいます。

filesystems.py内のソースを参考にFileWrapperを作成しています。
「__getattr__」ってこうやって使うのか。勉強になりました。

関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2015/01/18(日) 11:04:12|
  2. Python
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
<<Python + pyftpdlibでカスタムFTPサーバーを作成4 データベース連携 | ホーム | Python + pyftpdlibでカスタムFTPサーバーを作成2 ディレクトリの表示>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://symfoware.blog68.fc2.com/tb.php/1585-cc8e3245
この記事にトラックバックする(FC2ブログユーザー)